[go: up one dir, main page]

0% found this document useful (0 votes)
50 views1,133 pages

Core MVC - Model Binding Part 2

This ASP note help for your coureer

Uploaded by

vickydasuri111
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
50 views1,133 pages

Core MVC - Model Binding Part 2

This ASP note help for your coureer

Uploaded by

vickydasuri111
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 1133

1

ASP.NET Core MVC – Model Binding

What is Model Binding in ASP.NET Core?


Model Binding is a mechanism in the ASP.NET Core Application that extracts the data from
an HTTP request and provides them to the controller action method parameters or object
properties. The action method parameters can be simple types like integers, strings, etc., or
complex types such as Student, Order, Product, etc.
In ASP.NET Core MVC, Model Binding abstracts the complexities of dealing with raw HTTP
request data. That means we don’t need to think about how the data from different sources,
such as posted forms query strings, headers, route data, and JSON request bodies, are
mapped to action method parameters.
Example Without Using Model Binding in ASP.NET Core MVC
Let us first understand one example without using Model Binding, i.e., how to capture
request data in an ASP.NET Core MVC Application without Model Binding. In ASP.NET
Core MVC, FormCollection is a method for extracting data manually from HTTP requests.
Let us proceed and understand how to use FormCollection to fetch the data from HTTP
Requests.
Ad
1/2
00:12
Traveling across Quang Binh to stunning cinematic locations
First, create a new ASP.NET Core MVC Application named ModelBindingDemo. Then,
modify the HomeController as follows. As you can see, we are using FormCollection as a
parameter to capture the form data using the controller action method.
using Microsoft.AspNetCore.Mvc;
namespace ModelBindingDemo.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public string SubmitForm(IFormCollection form)
{
var UserName = form["UserName"].ToString();
var UserEmail = form["UserEmail"].ToString();
// Handle data as needed...
2

return $"User Created: UserName: {UserName}, UserEmail: {UserEmail}";


}
}
}
Understanding SubmitForm Action Method:
 The SubmitForm Action Method is decorated with the [HttpPost] Attribute.
[HttpPost] is an attribute that specifies that this action method should only
handle POST requests. POST requests are generally used to send data from
the client to the server, such as when submitting a form.
 SubmitForm(IFormCollection form) method handles the Form Submission.
It takes an IFormCollection parameter named form, representing the form
data sent in the HTTP request.
 var UserName = form[“UserName”].ToString(); and var UserEmail =
form[“UserEmail”].ToString(); extract the values for UserName and
UserEmail from the submitted form data. The keys “UserName” and
“UserEmail” correspond to the names of form inputs on the client-side form.
 The method concludes by returning a string that confirms the received data,
including the username and email address. In a real application, you would
typically handle the form data by saving it to a database or performing other
business logic instead of just returning it as a string.
Next, modify the Index.cshtml view as follows:
@{
ViewData["Title"] = "Home Page";
}
<form method="post" asp-controller="Home" asp-action="SubmitForm">
<label>Name:</label>
<input type="text" name="UserName" />
<br />
<label>Email:</label>
<input type="text" name="UserEmail" />
<br />
<button type="submit">Submit</button>
</form>
What is FormCollection in ASP.NET Core MVC?
In ASP.NET Core MVC, FormCollection is a class used to hold the data submitted from an
HTML form. Essentially, it provides a way to access form values sent via HTTP POST or
PUT requests. This class is part of the Microsoft.AspNetCore.Http namespace. It acts as a
collection of key-value pairs, where each key is a string representing the form field name,
and the value is a StringValues type representing the form field’s data. The following are the
limitations of FormCollection in ASP.NET Core MVC:
3

 Less Type Safety: Errors related to type mismatches or missing fields might
only be detected at runtime.
 No Model Validation: We need to handle validation and error messages
manually.
 Security: Directly accessing form values using FormCollection can cause
security issues like SQL injection or XSS attacks.
Using Model Binding in ASP.NET Core MVC
Let’s see how we can rewrite the previous example using Model binding. First, let’s create a
model to hold the UserName and UserEmail. So, create a class file with the
name User.cs within the Models folder and copy and paste the following code.
using System.ComponentModel.DataAnnotations;
namespace ModelBindingDemo.Models
{
public class User
{
[Required]
public string UserName { get; set; }
[Required]
public string UserEmail { get; set; }
}
}
Next, modify the Index.cshtml view as follows. As you can see, the User is now the model
for the following view.
@model User
@{
ViewData["Title"] = "Home Page";
}
<form method="post" asp-controller="Home" asp-action="SubmitForm">
<label>Name:</label>
<input asp-for="UserName" />
<br />
<label>Email:</label>
<input asp-for="UserEmail" />
<br />
<button type="submit">Submit</button>
</form>
4

Next, modify the HomeController as follows:


using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;
namespace ModelBindingDemo.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public string SubmitForm(User user)
{
if (user != null)
{
if (ModelState.IsValid)
{
// Handle data as needed...
return $"User Created: UserName: {user.UserName}, UserEmail: {user.UserEmail}";
}
}
return "Some Error Occured";
}
}
}
Now, run the application, and it should work as expected. Let’s proceed to understand
Model Binding in detail.
What is Model Binding in ASP.NET Core MVC?
Model binding in ASP.NET Core MVC is a mechanism where HTTP request data (such as
form values, route data, query string parameters, request body, and HTTP headers) is
automatically parsed and assigned to the parameters of an action method in a controller.
This allows us to work directly with strongly typed .NET objects in our action methods. This
will eliminate the need to manually parse and convert the raw HTTP request data to .NET
objects in the ASP.NET Core MVC Application. It also means that we don’t have to
5

manually extract data from the request; the framework handles it for us based on the
parameters of our action methods.
How Does Model Binding Work in ASP.NET Core?
When a request is made to an MVC controller, the model binding system:
 Retrieves Data: It looks at the source of the incoming HTTP request (like
form values, route data, query string parameters, request body, and HTTP
headers).
 Converts Data: It then attempts to convert these data values into .NET types
that match the parameters of the controller action being called.
 Validation: Optionally, model binding also works with data annotation
attributes to validate data as it’s converted into types.
Note: The model binder looks for data in the request that matches the names of the action
method’s parameters. Depending on the source attribute (e.g., [FromQuery], [FromBody],
[FromRoute]), it knows where to look, such as in the query string, the request body, or the
route data.
Different Model Binding Techniques in ASP.NET Core MVC:
You can configure model binding using various attributes that control how data is extracted
from the request. Some common attributes include:
 [FromBody]: To bind data from the request body.
 [FromForm]: To bind data from form fields.
 [FromQuery]: To bind data from the query string.
 [FromRoute]: To bind data from route data.
 [FromHeader]: To bind data from headers.
Note: We will discuss all these model-binding techniques in detail in our next article. This
article will help us understand the basics of these model binding techniques and how we
can use them in our controller action methods.
FromBody:
This attribute tells ASP.NET Core to bind data from the request body to the parameter of an
action method. It’s commonly used when complex types like objects or collections, typically
in JSON or XML format, are expected in the body of a POST request. It is commonly used
in Restful Services.
[HttpPost]
public IActionResult Create([FromBody] Product product)
{
//...
}
FromForm:
The [FromForm] attribute binds data from HTML form fields to action method parameters.
It’s suitable for handling form submissions via HTTP POST requests and is commonly used
in ASP.NET Core MVC Applications.
[HttpPost]
public IActionResult Register([FromForm] UserRegistrationModel user)
{
6

//...
}
FromQuery:
The [FromQuery] attribute binds data from the URL’s query string to action method
parameters. It’s often used for GET requests where data is passed in the URL.
public IActionResult Search([FromQuery] string query)
{
//...
}
FromRoute:
The [FromRoute] attribute is used to bind data from route parameters to action method
parameters. Route parameters are placeholders in the URL defined in your route templates.
[HttpGet("{id}")]
public IActionResult Get([FromRoute]int id)
{
//...
}
FromHeader:
The [FromHeader] attribute binds data from HTTP headers to action method parameters.
It’s useful for extracting custom headers or standard headers like Authorization, User-Agent,
etc.
public IActionResult CheckUserAgent([FromHeader(Name = "User-Agent")] string
userAgent)
{
//...
}
Why Model Binding in ASP.NET Core?
There are several reasons why model binding is important. They are as follows:
 Automatic Mapping: Model binding automatically maps data from HTTP
requests to action method parameters. Each parameter’s data source might
include the request body, query string, headers, cookies, or route data.
 Support for Complex Types: It supports binding simple types like integers
and strings, as well as complex types like custom classes and collections. For
complex types, model binding looks for matching keys in the incoming data
and assigns values to properties or fields in the object.
 Customization and Extensibility: Developers can customize the binding
process by using attributes like [FromBody], [FromQuery], [FromRoute],
[FromHeader], [FromForm], and [FromServices]. These attributes specify the
source of the data for a particular action method parameter.
7

 Validation: Model binding works closely with model validation. Once data is
bound to parameters, it can be validated using data annotations and custom
validation rules. Invalid model states can be handled within the action
methods.
Built-in Model Binders in ASP.NET Core MVC:
In ASP.NET Core MVC, model binding involves a sequence of providers, each designed to
handle different types of input data. The built-in model binders are integrated into the
framework and configured to work in a specific sequence that is generally suitable for most
use cases.
The default order of these providers is designed to handle most typical scenarios effectively.
This order determines how the framework attempts to bind incoming data to action method
parameters or properties of complex types. To see the same, please add the following code
to the Program.cs class file. It will display the default order of all the registered Model
Binders in the ASP.NET Core Application. Once you run the application, check the Console
Window.
builder.Services.AddControllersWithViews(options =>
{
foreach (var provider in options.ModelBinderProviders)
{
Console.WriteLine(provider.GetType().ToString());
}
});
If you want to control the “priority” or decide which source to use, then you need to specify
the appropriate attribute explicitly on each action method parameter. For example, if you
have an action method that could receive a userId either from the query string or as a JSON
payload, then you can decorate the parameter using either [FromQuery] or [FromBody] as
follows:
public IActionResult GetUser([FromQuery] int userId)
{
// Uses userId from the query string
}
// OR
public IActionResult UpdateUser([FromBody] User user)
{
// User object is expected as a JSON body which includes userId
}
8

Model Binding using FromForm in ASP.NET Core MVC:


In this article, I will discuss How to Use FromForm to Perform Model Binding in an
ASP.NET Core MVC Application with Examples. Please read our previous article
discussing the basic concepts of Model Binding in ASP.NET Core MVC.
Model Binding using FromForm in ASP.NET Core MVC
Model Binding in ASP.NET Core MVC simplifies handling HTTP requests by automatically
mapping data from the request to action method parameters. When we use the FromForm
attribute in ASP.NET Core MVC Controller action, we instruct the MVC Framework to bind
input values from the HTTP POST or PUT request form (i.e., form fields) to a model or
method parameter.
This is useful in scenarios where you deal with forms that submit data to the server. This is
common in ASP.NET Core MVC applications, where we created HTML forms that use the
POST or PUT method to submit data to the server. If you go to the definition of the
FromFormAttribute class, you will see the following.

Traveling across Quang Binh to stunning cinematic locations00:13 / 03:1710 Sec


This attribute belongs to Microsoft.AspNetCore.Mvc namespace, which automatically
maps HTTP request data to action method parameters. The FromFormAttribute targets data
sent via HTML form submissions, particularly those submitted using POST and PUT
methods where the content type is typically application/x-www-form-
urlencoded or multipart/form-data (for forms that include file uploads).
 BindingSource Property: The BindingSource property of the
FromFormAttribute specifies where the data should be bound from. Each
model binding attribute in ASP.NET Core has a corresponding BindingSource
that indicates the source of the data it is meant to handle. For
FromFormAttribute, this is set to BindingSource.Form, which tells the model
binder to look for data in the form body of the HTTP request.
 Name Property: This is particularly useful in cases where the form fields
submitted by the client do not match the parameter names in the method
signature. Here, using the Name Property of the FromForm Attribute, we can
specify the name that matches the input form field name.
Example to Understand FromForm Attribute in ASP.NET Core MVC
Let us understand this with an example. First, create a class representing the form data. For
example, if we have a form for user registration, we need to create a model with properties
9

such as ID, Name, Email, Password, and Mobile. So, first, create a class file
named User.cs within the Models folder and then copy and paste the following code into it.
namespace ModelBindingDemo.Models

public class User

public int Id { get; set; }

public string? Name { get; set; }

public string? Email { get; set; }

public string? Password { get; set; }

public string? Mobile { get; set; }

}
Create an Action Method:
Next, in our controller, we need to define an action method to handle the form submission.
Use the FromForm attribute to specify that the action method should accept data from the
form. So, create a UsersController controller within the Controllers folder and copy and
paste the following code. The Create action method, decorated with the HttpPost attribute,
will handle the form POST request.
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

[HttpGet("users/create")]

public IActionResult Create()

{
10

// Return the view that has the form to create a user

return View();

[HttpPost("users/create")]

public IActionResult Create([FromForm] User user)

if (!ModelState.IsValid)

// Return the form view with the posted data and validation messages

return View(user);

// Process the Model, i.e., Save user to database

// Redirect to another action after successful operation

return RedirectToAction("SuccessAction");

public string SuccessAction()

return "User Created Successfully";

}
In the above example, when the form is submitted, the data from the form body is
automatically bound to the User object of the Create action method (Create method, which
is decorated with HttpPost Attribute) using the FromForm attribute. The FromForm attribute
maps the posted form data to the User object.
Create a Form in a View:
11

Next, we need to create a view, and inside the view, we need to create a form using which
the user submits his data. The name attributes of the form inputs must match the properties
of the model. So, create the Create.cshtml view within the Views/Users directory. Once
you create the Create.cshtml view, copy and paste the following code.
@model User

@{

ViewData["Title"] = "Create";

<h1>User Registration Form</h1>

<form method="post" asp-controller="Users" asp-action="create" class="p-4">

<div class="mb-3">

<label for="Name" class="form-label">Name</label>

<input asp-for="Name" class="form-control" placeholder="Enter Your Name" />

</div>

<div class="mb-3">

<label for="Email" class="form-label">Email</label>

<input asp-for="Email" class="form-control" placeholder="Enter Your Email" />

</div>

<div class="mb-3">

<label for="Password" class="form-label">Password</label>

<input asp-for="Password" type="password" class="form-control" placeholder="Enter Your


Password" />

</div>

<div class="mb-3">

<label for="Mobile" class="form-label">Mobile</label>


12

<input asp-for="Mobile" type="Mobile" class="form-control" placeholder="Enter Your


Mobile Number" />

</div>

<button type="submit" class="btn btn-primary">Register</button>

</form>
Now, run the application and create a user by visiting the /users/create URL. You will see
the respective view to create the user, as expected. Provide the details in the view and click
the Submit button, as shown in the image below.

Using Primitive Type with FromForm Attribute:


Instead of using the Complex type, we can also use the primitive data type along with
the FromForm attribute. To better understand, please modify the UsersController as
follows.
using Microsoft.AspNetCore.Mvc;

namespace ModelBindingDemo.Controllers
13

public class UsersController : Controller

[HttpGet("users/create")]

public IActionResult Create()

// Return the view that has the form to create a user

return View();

[HttpPost("users/create")]

public IActionResult Create([FromForm] string Name, [FromForm] string Email,


[FromForm] string Password, [FromForm] string Mobile)

// Validate the Data

// Save user to database

// Redirect to another action after successful operation

return RedirectToAction("SuccessAction");

public string SuccessAction()

return "User Created Successfully";

}
14

How Does Model Binding Work with FromForm Attribute in ASP.NET


Core?
When a user submits a form on a web page, the browser packages the data entered into
the form fields and sends it to the server as part of the request body. The data can be
encoded as application/x-www-form-urlencoded where the form data is encoded as key-
value pairs (similar to query strings), or multipart/form-data when the form includes file
uploads. The following is a detailed step-by-step process of how model binding works with
the FromForm attribute:
 Step 1: Identify the Source: The model binder recognizes that the binding
source is the request body’s form data due to the FromForm attribute.
 Step 2: Match Form Fields to Parameters: It then tries to match the names
of the form field fields with the names of the action method parameters. If the
names match, it binds the values to those parameters. If a Name property is
set in the FromForm attribute, that name will be used to look up form data
instead of the parameter name.
 Step 3: Conversion and Binding: The model binder converts the form data
(which is initially in string format) to the types of the respective action method
parameters. ASP.NET Core has built-in type converters to handle this for
most common data types, including custom complex types.
 Step 4: Handling Errors and Validation: If errors occur during binding (like
type mismatches) or validation fails based on data annotations or custom
validators, the model binder updates the ModelState with these errors. Then,
we can check the ModelState.IsValid to determine if there were issues with
the incoming data.
Using the Name Property of FromFormAttribute:
Suppose you have a form field named Email, but in your model or method, you want to bind
it to a property or parameter named UserEmail. Again, we want to map the Name form field
with the UserName parameter. In this case, we can use the Name property of the
FromForm Attribute to map these correctly. For a better understanding, please modify the
UsersController as follows:
using Microsoft.AspNetCore.Mvc;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

[HttpGet("users/create")]

public IActionResult Create()

// Return the view that has the form to create a user


15

return View();

[HttpPost("users/create")]

public IActionResult Create([FromForm(Name ="Name")] string UserName,


[FromForm(Name ="Email")] string UserEmail, [FromForm] string Password, [FromForm]
string Mobile)

// Validate the Data

// Save user to database

// Redirect to another action after successful operation

return RedirectToAction("SuccessAction");

public string SuccessAction()

return "User Created Successfully";

}
Now, with the above changes in place, run the application, and it should work as expected.
When Should We Use FromForm Attribute in ASP.NET Core MVC?
In ASP.NET Core MVC, the FromForm attribute indicates that a parameter should be bound
from the form data in the request body. The following are some scenarios where we can
use the FromForm attribute:
Traditional HTML Forms:
One of the most common use cases for FromForm is when dealing with traditional HTML
forms.
<form method="post" action="/submit">

<input type="text" name="username" />


16

<input type="password" name="password" />

<input type="submit" value="Submit" />

</form>
When the form is submitted, the data is sent in the request body. The FromForm attribute
binds this data to the action method’s parameters.
File Uploads:
When handling file uploads with a form using enctype=”multipart/form-data”, the
FromForm attribute is used to bind uploaded files and any additional form fields.
<form method="post" action="/upload" enctype="multipart/form-data">

<input type="file" name="uploadedFile" />

<input type="text" name="description" />

<input type="submit" value="Upload" />

</form>
In the corresponding action method, you might have:
[HttpPost("/upload")]

public IActionResult Upload([FromForm] IFormFile uploadedFile, [FromForm] string


description)

// Handle file and description

}
When Not to Use FromForm Attribute?
If we are building an API, i.e., Restful Services, that will be consumed by other clients, such
as mobile apps or frontend frameworks like React, Angular, or Vue.js, where we generally
work with JSON payloads rather than form data, we should use FromBody instead of
FromForm Attribute.

Model Binding using FromQuery in ASP.NET Core MVC


In this article, I will discuss How to Use FromQuery to Perform Model Binding in an
ASP.NET Core MVC Application with Examples. Please read our previous article
discussing Model Binding using FromForm in ASP.NET Core MVC. The FromQuery
attribute is commonly used in API development and situations where data is passed via
URL parameters rather than in the body of the request.
Model Binding using FromQuery in ASP.NET Core MVC
17

In ASP.NET Core MVC, model binding allows you to map data from various sources (such
as query strings, form fields, and JSON bodies) to the parameters of your action methods.
The FromQuery attribute specifically tells the ASP.NET Core MVC framework to bind data
from the query string of the request to parameters in your action method. The query string is
part of a URL after the “?” Character and is composed of key-value pairs separated
by & characters.
When you decorate an action method parameter with the [FromQuery] attribute, ASP.NET
Core MVC automatically binds data from the query string to that parameter based on its
name. If you go to the definition of FromQueryAttribute, you will see the following signature.

Traveling across Quang Binh to stunning cinematic locations00:16 / 03:1710 Sec


In ASP.NET Core MVC, the BindingSource and Name property of the FromQueryAttribute
provide detailed control over how data from the request’s query string is mapped to the
parameters of an action method.
 BindingSource: BindingSource specifies where the value for a parameter is
found. For example, in the context of the FromQueryAttribute, ASP.NET Core
uses the BindingSource.Query automatically. This means the system is
configured to look for values specifically in the query part of the incoming
HTTP request URL.
 Name: The Name property of FromQueryAttribute is important when the
parameter name in your action method does not match the key in the query
string. It explicitly maps a query string key to a method parameter, offering
flexibility in how parameters are named in your method versus what is
exposed in URLs.
Example to Understand FromQueryAttribute in ASP.NET Core MVC:
Suppose we have a scenario where we search for users based on Name and Age. So, let
us first create a model to hold the search string. Create a class file
named UserSearchCriteria.cs within the Models folder and copy and paste the following
code.
namespace ModelBindingDemo.Models

public class UserSearchCriteria

public string Name { get; set; }

public int Age { get; set; }


18

}
Next, create another class file named User.cs within the Models folder and copy and paste
the following code. This is a simple model that represents a user.
namespace ModelBindingDemo.Models

public class User

public int Id { get; set; }

public string Name { get; set; }

public int Age { get; set; }

public string? Mobile { get; set; }

}
Next, create an Empty MVC Controller named UsersController within the Controllers and
copy and paste the following code. In the following example, we are binding query
parameters to a model, but you can also bind them directly to action method primitive
parameters. Please ensure the null values are handled, or default values are provided if
some query parameters are optional. In the example below, int? Age means age can be
null.
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

private List<User> _users;

public UsersController()
19

_users = new List<User>()

new User(){Id =1, Name ="Pranaya", Age = 35},

new User(){Id =2, Name ="Priyanka", Age = 30},

new User(){Id =3, Name ="Anurag", Age = 35},

new User(){Id =4, Name ="Prateek", Age=30},

new User(){Id =5, Name ="Hina", Age=35}

};

[HttpGet("users/search")]

public IActionResult Search([FromQuery] UserSearchCriteria criteria)

List<User> FilteredUsers = new List<User>();

if (criteria != null)

if (!string.IsNullOrEmpty(criteria.Name) && criteria.Age > 0)

FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(criteria.Name.ToLower())


|| x.Age > criteria.Age).ToList();

else if(!string.IsNullOrEmpty(criteria.Name))

{
20

FilteredUsers = _users.Where(x =>


x.Name.ToLower().StartsWith(criteria.Name.ToLower())).ToList();

else if (criteria.Age > 0)

FilteredUsers = _users.Where(x => x.Age > criteria.Age).ToList();

return Ok(FilteredUsers);

}
Understanding the Search Action Method:
The method is decorated with the [HttpGet] attribute, indicating that it will respond to HTTP
GET requests only. The route template “users/search” specifies the endpoint URL for
accessing this method. The method takes a parameter criteria of type UserSearchCriteria,
decorated with [FromQuery] attribute. This means that the method expects the values for
criteria to be provided in the query string of the request URL.
The Logic of the Search Method:
 The method initializes an empty list called FilteredUsers, which will store the
users that match the search criteria. Then, it checks if the criteria object is not
null. If the criteria is not null, it checks whether the Name property or the Age
property (or both) of the criteria object has been provided.
 Depending on the provided criteria, it filters the _users list accordingly. If both
Name and Age are provided, it filters users whose names start with the
provided name (case-insensitive) or whose age is greater than the provided
age. If only the Name is provided, it filters users whose names start with the
provided name (case-insensitive). If only Age is provided, it filters users
whose age is greater than the provided age.
 Finally, it returns an HTTP response with status code 200 (OK) and the
filtered list of users.
Return Value:
 The method returns an IActionResult, which is a common base type for all
action results in ASP.NET Core MVC. If the filtering logic executes
successfully, it returns an HTTP 200 OK response and the filtered list of users
(FilteredUsers).
21

Testing the Action Method:


When calling the above endpoint, you can provide search criteria through the query string
as follows:
 /users/search?Name=pr
 /users/search?Age=30
 /users/search?Name=Pr&Age=30
In the above cases, the values from the query string (Name and Age) will be bound to the
properties of the UserSearchCriteria object. This is possible because of the FromQuery
attribute.
Using Primitive Type with FromQuery Attribute
Instead of using the Complex type, we can also use the primitive data type along with the
FromQuery attribute. For a better understanding, please modify the UsersController as
follows:
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

private List<User> _users;

public UsersController()

_users = new List<User>()

new User(){Id =1, Name ="Pranaya", Age = 35},

new User(){Id =2, Name ="Priyanka", Age = 30},

new User(){Id =3, Name ="Anurag", Age = 35},

new User(){Id =4, Name ="Prateek", Age=30},

new User(){Id =5, Name ="Hina", Age=35}

};
22

[HttpGet("users/search")]

public IActionResult Search([FromQuery] string Name, [FromQuery] int? Age)

List<User> FilteredUsers = new List<User>();

if (!string.IsNullOrEmpty(Name) && Age != null && Age > 0)

FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower()) ||


x.Age > Age).ToList();

else if (!string.IsNullOrEmpty(Name))

FilteredUsers = _users.Where(x =>


x.Name.ToLower().StartsWith(Name.ToLower())).ToList();

else if (Age != null & Age > 0)

FilteredUsers = _users.Where(x => x.Age > Age).ToList();

return Ok(FilteredUsers);

}
23

If the parameter names and query string keys match, you don’t strictly need the FromQuery
attribute for binding to work. For a better understanding, please modify
the UsersController as follows:
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

private List<User> _users;

public UsersController()

_users = new List<User>()

new User(){Id =1, Name ="Pranaya", Age = 35},

new User(){Id =2, Name ="Priyanka", Age = 30},

new User(){Id =3, Name ="Anurag", Age = 35},

new User(){Id =4, Name ="Prateek", Age=30},

new User(){Id =5, Name ="Hina", Age=35}

};

[HttpGet("users/search")]

public IActionResult Search(string Name, int? Age)

List<User> FilteredUsers = new List<User>();


24

if (!string.IsNullOrEmpty(Name) && Age != null && Age > 0)

FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower()) ||


x.Age > Age).ToList();

else if (!string.IsNullOrEmpty(Name))

FilteredUsers = _users.Where(x =>


x.Name.ToLower().StartsWith(Name.ToLower())).ToList();

else if (Age != null & Age > 0)

FilteredUsers = _users.Where(x => x.Age > Age).ToList();

return Ok(FilteredUsers);

}
Note: Using the attribute explicitly clarifies your intention and makes the code more
maintainable and understandable.
How Does Model Binding Work with FromQuery Attribute in ASP.NET
Core MVC?
When a request is made to an MVC action that has parameters decorated with the
FromQuery attribute, the model binding system kicks in with the following steps:
 Identify the Source: The model binder identifies the query string as the
source of the binding data due to the FromQuery attribute.
 Match Parameters: The model binder matches keys in the query string with
the names of the action method parameters. If a Name property is specified
in the FromQuery attribute, it uses this custom name to match the keys in the
query string.
25

 Conversion and Binding: Once matching keys are found, the model binder
converts the query string values from strings to the types of the respective
action method parameters. ASP.NET Core has built-in type converters that
handle most primitive types and can bind to complex types by matching the
query string keys to property names of the complex types.
 Handling Errors: If there are binding errors, such as a mismatch in type
conversion, ASP.NET Core handles these, typically assigning default values
or nulls depending on the scenario and configuration. Errors can also be
handled or logged by checking the model state (ModelState.IsValid).
Using Name Property of FromQuery Attribute:
The Name property of the FromQueryAttribute is used when the name of the parameter in
the action method does not match the key in the query string. It provides a way to map the
query string key directly to a method parameter that might have a different name.
Suppose you have a query string named EmpAge, but in your method, you want to bind it
to a parameter named Age. Again, you want to map the EmpName query string with
the Name parameter of our action method. In this case, we need to use the Name property
to map these correctly. For a better understanding, please modify the UsersController as
follows:
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

private List<User> _users;

public UsersController()

_users = new List<User>()

new User(){Id =1, Name ="Pranaya", Age = 35},

new User(){Id =2, Name ="Priyanka", Age = 30},

new User(){Id =3, Name ="Anurag", Age = 35},

new User(){Id =4, Name ="Prateek", Age=30},


26

new User(){Id =5, Name ="Hina", Age=35}

};

[HttpGet("users/search")]

public IActionResult Search([FromQuery(Name ="EmpName")] string Name,


[FromQuery(Name ="EmpAge")] int? Age)

List<User> FilteredUsers = new List<User>();

if (!string.IsNullOrEmpty(Name) && Age != null && Age > 0)

FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower()) ||


x.Age > Age).ToList();

else if (!string.IsNullOrEmpty(Name))

FilteredUsers = _users.Where(x =>


x.Name.ToLower().StartsWith(Name.ToLower())).ToList();

else if (Age != null & Age > 0)

FilteredUsers = _users.Where(x => x.Age > Age).ToList();

return Ok(FilteredUsers);

}
27

}
Now, while calling the above action method, you can provide search criteria through the
query string as follows:
 /users/search?EmpName=pr
 /users/search?EmpAge=30
 /users/search?EmpName=Pr&EmpAge=30
When Should We Use FromQuery Attribute in ASP.NET Core?
The FromQuery Attribute in ASP.NET Core can be useful for handling HTTP GET requests
where you want to pass information through the URL. The following are a few scenarios
where you might use the FromQuery attribute:
 Filtering Data: It’s common to use query parameters to filter data in APIs. For
instance, if you have an endpoint that retrieves a list of products, you can use
FromQuery to allow clients to filter products based on criteria like category,
price range, etc.
 Pagination: For endpoints that return a collection of items, query parameters
can be used to implement pagination. Using FromQuery, you can specify
parameters such as page and pageSize to control how many items to return
and which data page to retrieve.
 Search Parameters: If you have a search endpoint, you can use FromQuery
to capture search criteria sent by the client, such as search keywords, date
ranges, or other filters.
 Sorting: When returning a list of items, you might want to allow the user to
specify sorting criteria through the query string, such as sorting by date,
name, or price.
When Not to Use:
 Sensitive Data: Never pass sensitive data in the query string, like passwords
or security tokens. They can be logged in server logs, browser history, or
referrer headers, posing a security risk.
 Very Large Data Sets: Browsers and servers have URL length limits. If the
query string becomes too long, it’s better to use a different method, like
sending a JSON payload in the POST request body.

Model Binding using FromRoute in ASP.NET Core MVC


In this article, I will discuss How to Use FromRoute to Perform Model Binding in ASP.NET
Core MVC Applications with Examples. Please read our previous article
discussing FromQuery in ASP.NET Core MVC.
Model Binding Using FromRoute in ASP.NET Core MVC
In ASP.NET Core MVC, the FromRoute attribute is used to specify that an action method
parameter should be bound using data from the route of the HTTP request. This means
values embedded in the URL path, not the query string or request body. Route values are
typically specified using tokens in route templates. For example, in a route
like /users/{id}, the {id} is a route token that can be bound to a method parameter. If you
go to the definition of FromRouteAttribute, you will see the following signature.
28

In ASP.NET Core MVC, the BindingSource and the Name property of the FromRoute
attribute are essential for controlling how data is parsed and bound to action method
parameters.
Ad
1/2
00:19

Traveling across Quang Binh to stunning cinematic locations


 BindingSource: BindingSource specifies where the value for a parameter is
found. For example, in the context of the FromRouteAttribute, ASP.NET Core
uses the BindingSource.Route automatically. This means the system is
configured to look for values specifically in the route part of the incoming
HTTP request URL.
 Name: The Name property of the FromRoute attribute specifies an alternative
name for a route parameter that binds to the action method parameter. This is
useful when the name of the route parameter defined in the route template
differs from the name of the action method parameter to which it needs to be
bound.
Example to understand FromRoute Attribute in ASP.NET Core MVC:
Let’s assume we are building an API to get user details based on their ID. Here is how you
can use the FromRoute attribute. First, create a class file with the name User.cs within
the Models folder, and then copy and paste the following code. This is a simple model that
represents a user.
namespace ModelBindingDemo.Models

public class User

public int Id { get; set; }

public string? Name { get; set; }

public int Age { get; set; }

public string? Mobile { get; set; }


29

}
Next, create a new Empty MVC controller named UsersController within the Controllers
folder and then copy and paste the following code.
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

private List<User> _users;

public UsersController()

_users = new List<User>()

new User(){Id =1, Name ="Pranaya", Age = 35},

new User(){Id =2, Name ="Priyanka", Age = 30},

new User(){Id =3, Name ="Anurag", Age = 35},

new User(){Id =4, Name ="Prateek", Age=30},

new User(){Id =5, Name ="Hina", Age=35}

};

[HttpGet]

[Route("users/{Id}/getdetails")]

public IActionResult GetUserById([FromRoute] int Id)


30

// Here, you can use the 'id' to fetch user details from a database or other data sources.

var user = _users.FirstOrDefault(x => x.Id == Id);

if (user == null)

return NotFound();

return Ok(user);

}
When you want to fetch a user with ID 2, you will access the URL: users/2/getdetails. The
2 in the URL would be bound to the Id parameter in the GetUserById action, which is
possible because of the FromRoute attribute.
Using Name Property of FromRoute Attribute:
The Name property of the FromRoute Attribute is used when the name of the route
parameter defined in the route template is different from the name of the action method
parameter to which it needs to be bound. So, it provides a way to map the route data
directly to a method parameter with a different name.
Suppose you have a Route Token Named ID, but in your method, you want to bind it to a
parameter named UserId. In this case, you need to use the Name property to map these
correctly. For a better understanding, please modify the UsersController as follows:
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller

private List<User> _users;


31

public UsersController()

_users = new List<User>()

new User(){Id =1, Name ="Pranaya", Age = 35},

new User(){Id =2, Name ="Priyanka", Age = 30},

new User(){Id =3, Name ="Anurag", Age = 35},

new User(){Id =4, Name ="Prateek", Age=30},

new User(){Id =5, Name ="Hina", Age=35}

};

[HttpGet]

[Route("users/{Id}/getdetails")]

public IActionResult GetUserById([FromRoute(Name ="Id")] int UserId)

// Here, you can use the 'id' to fetch user details from a database or other data sources.

var user = _users.FirstOrDefault(x => x.Id == UserId);

if (user == null)

return NotFound();

return Ok(user);

}
32

}
How Does Model Binding Work with FromRoute Attribute in ASP.NET
Core?
The FromRoute attribute is used to bind action method parameters to parameters from the
route data in an HTTP request. The process of model binding with FromRoute involves
several steps:
 Route Template Matching: When a request is received, ASP.NET Core
matches the URL to the route templates defined in your controllers or
program configuration. Each segment of the URL defined as a parameter
within curly braces ({ }) is parsed as route data.
 Identify the Source: The model binder identifies the route parameters as the
data source because the FromRoute attribute applied to the action method
parameters makes this possible.
 Match and Bind Parameters: The model binder matches the names of the
parameters in the route data with the names of the parameters in your action
method. If they match, it binds the values from the route data to those
parameters. If a Name property is specified in the FromRoute attribute, it
overrides the default matching to use this specified name instead.
 Conversion: The values extracted from the route are converted from strings
(as all route data is captured as strings) to the types of the respective
parameters in the action method. ASP.NET Core uses built-in type converters
for most primitive types.
 Validation: Once the values are bound, any validation attributes applied to
the action method parameters are processed. If the validation fails, the errors
are added to ModelState.
When Should We Use FromRoute Attribute in ASP.NET Core MVC?
The FromRoute attribute is useful when you want to retrieve data directly from the request
URL. The following are some of the scenarios where you can use FromRoute Attribute:
 RESTful APIs: In RESTful API design, resource identifiers are often part of
the URL. For example, if you have an API endpoint that fetches a specific
user by their ID, the user ID might be part of the route. Using FromRoute lets
you directly bind this user ID from the route to a method parameter in your
controller.
 Avoiding Query Strings for Mandatory Data: If certain data is essential for
the operation of a specific endpoint (like an entity ID in CRUD operations),
embedding it in the URL (and using FromRoute to bind it) can be more
appropriate than relying on query strings, which are better suited for optional
parameters.
 Hierarchy in Data: When you are dealing with hierarchical resources where
one resource is a subset of another, using route data is more semantically
correct. For example, accessing comments on a blog post might look like
/posts/123/comments/456, where both 123 and 456 can be bound using
FromRoute.
When Not to Use FromRoute?
 Query-Specific Data: If the data represents filters, sorts, or other query-
specific data, it might be better suited for the query string (i.e., use
FromQuery).
33

 Large or Complex Data: Route data should be kept simple and limited to
identifiers or other small data bits. If you are passing complex objects, those
should likely be sent in the request body (i.e., use FromBody).
 Sensitive Data: Don’t send sensitive information in the URL, even if it’s in the
route. URLs can be logged, cached, or shared, which could expose sensitive
information.

Model Binding using FromHeader in ASP.NET Core MVC


I will discuss How to Use FromHeader to Perform Model Binding in ASP.NET Core MVC
with Examples in this article. Please read our previous article discussing FromRoute in
ASP.NET Core MVC.
Model Binding using FromHeader in ASP.NET Core MVC
In ASP.NET Core MVC, the FromHeader attribute is used to specify that a parameter
should be bound using data from the header of the HTTP request. Headers of an HTTP
Request provide a way to send additional information about a request or response. This
includes common standard headers (e.g., User-Agent, Content-Type), and you can also
pass custom headers.
The FromHeader attribute is especially useful when working with API endpoints that require
specific header data to process a request, such as authentication tokens or custom client
metadata. If you go to the definition of FromBodyAttribute, you will see the following
signature.

Traveling across Quang Binh to stunning cinematic locations00:04 / 03:1710 Sec


The FromHeaderAttribute specifies that a parameter or property should be bound using the
request headers.
Example to understand FromHeaderAttribute in ASP.NET Core MVC:
Let’s assume we have an API endpoint where the client provides a version number through
a custom header, and based on this version, the server might change its response. So,
create a controller named UsersController and copy and paste the following code into it.
using Microsoft.AspNetCore.Mvc;

namespace ModelBindingDemo.Controllers

public class UsersController : Controller


34

[Route("users/data")]

[HttpGet]

public IActionResult GetUsers([FromHeader(Name = "X-Api-Version")] string apiVersion)

if (string.IsNullOrEmpty(apiVersion))

return BadRequest("X-Api-Version header is required.");

if (apiVersion == "1.0")

// Return data specific to version 1.0

return Ok(new { version = "1.0", data = "Data for v1.0" });

else if (apiVersion == "2.0")

// Return data specific to version 2.0

return Ok(new { version = "2.0", data = "Data for v2.0" });

else

return BadRequest($"Unsupported API version: {apiVersion}");

}
35

}
Request with Headers:
Clients can request the /users/data endpoint and set the X-Api-Version header to 1.0 or
2.0 to fetch version-specific data.
Points to Remember:
 The Name property in the FromHeader attribute is used to specify the
header’s name from which the data should be bound. If the parameter name
matches the header name, the Name property isn’t strictly necessary, but it’s
a good practice to include it for clarity.
 Always validate incoming header data. Clients can manipulate headers, so
don’t trust them implicitly. In this example, we perform basic validation by
checking for null, empty, and known versions.
When to use FromHeader Attribute in ASP.NET Core MVC?
In ASP.NET Core MVC, the FromHeader attribute binds action method parameters to
specific HTTP header values. Here are scenarios in which you might use the FromHeader
attribute:
Authentication & Authorization:
The Authorization header is often used to pass tokens (like JWTs) or credentials to the
server.
[HttpGet]

public IActionResult Get([FromHeader(Name = "Authorization")] string token)

// Validate token and proceed

}
Content Negotiation:
Clients can use the Accept header to specify the expected media type (e.g.,
application/json, text/xml). The server can then use this header to determine how to format
the response. Similarly, the Content-Type header can inform the server about the data
format in the request body.
Caching Control:
Headers like If-Modified-Since or If-None-Match can be used for conditional requests. The
server can check these headers and decide whether to send a full response or a 304 Not
Modified status.
Localization:
The Accept-Language header can specify the client’s language preferences. Based on this,
the server can return localized content.
[HttpGet]
36

public IActionResult GetLocalizedContent([FromHeader(Name = "Accept-Language")]


string language)

// Return content based on the provided language preference

}
Custom Headers for Business Logic:
Sometimes, apps define custom headers for specific business requirements. For instance,
an X-User-Tier header could denote the subscription tier of the user, affecting the kind of
data or features available to them.
Rate Limiting:
If you’re implementing rate limiting, headers like X-RateLimit-Limit and X-RateLimit-
Remaining can be utilized to communicate rate limit details back to the client.
Debugging or Diagnostics:
Some applications use headers for diagnostics or tracing. For example, a header might
contain a trace ID propagated through various microservices to track a request.
When Not to Use:
 Regular Payload Data: Headers are not meant for standard request payload
data. Use FromBody, FromQuery, or FromRoute for that.
 Sensitive Data: It’s best to avoid sending sensitive data in headers,
especially if unnecessary. While headers are part of the encrypted content in
HTTPS traffic, they can be logged in server logs or exposed in other parts of
the processing pipeline.
So, FromHeader provides a way to access specific HTTP headers and can be vital for
certain scenarios, especially around authentication, content negotiation, and other HTTP-
specific functionalities. However, it’s important to use it judiciously and avoid overloading
headers with unnecessary data.

Model Binding using FromBody in ASP.NET Core MVC


In this article, I will discuss How to Use FromBody to Perform Model Binding in
ASP.NET Core MVC with Examples. Please read our previous article
discussing FromHeader in ASP.NET Core MVC.
Model Binding using FromBody in ASP.NET Core MVC
Model binding in ASP.NET Core MVC maps incoming HTTP request data to action method
parameters. When data is sent in the body of an HTTP request (typically for POST, PUT,
and PATCH methods), it usually comes as JSON or XML. In such cases, you need to use
the FromBody attribute to bind this incoming data to a parameter in your action method. If
you go to the definition of FromBodyAttribute, you will see the following signature.
37

The FromBodyAttribute specifies that a parameter or property should be bound using the
request body.
Ad
1/2
00:19

Traveling across Quang Binh to stunning cinematic locations

Example to Understand FromBodyAttribute in ASP.NET Core MVC:


First, create a class file with the name User.cs within the Models folder, and then copy and
paste the following code. This is a simple model that represents a user.
namespace ModelBindingDemo.Models

public class User

public int Id { get; set; }

public string? Name { get; set; }

public int Age { get; set; }

public string? Mobile { get; set; }

}
Create a controller action and use the FromBody attribute to bind the incoming request
body to your model. Create a controller named UsersController and then copy and paste
the following code into it.
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers
38

public class UsersController : Controller

[Route("users/create")]

[HttpPost]

public IActionResult Create([FromBody] User user)

if (!ModelState.IsValid)

return BadRequest(ModelState);

// Handle the user data: save it to a database, perform some operations, etc.

// Often, you might return the created entity or just a success message.

return Ok(user);

}
Making a Request:
Clients can then send a POST request with a JSON body to the /users/create endpoint:
{

"Id": 10,

"Name": "Kumar",

"Age": 30,

"Mobile":"1234567890"

}
39

The FromBody attribute will take this JSON data, attempt to deserialize it into a User object
and provide it to your action method as the user parameter.
Points to Remember:
 Ensure you have a suitable JSON serializer configured for your application.
For many applications in ASP.NET Core, the default JSON serializer (like
Newtonsoft.Json in older versions or System.Text.Json in newer versions) is
used.
 If deserialization fails (e.g., the provided JSON doesn’t match the User
structure or contains invalid data), the model binding will mark the model as
invalid. This is why you check ModelState.IsValid in your action method. If it’s
false, it means something went wrong with the binding.
 You can return a bad request or any other appropriate response when the
model state is invalid. The ModelState will contain error messages about what
went wrong, which can be useful for debugging or informing clients about the
nature of the error.
 In ASP.NET Core 3.x and later, you no longer need to specify [FromBody] for
complex types; it’s inferred by default. However, it’s still a good practice to
include it for clarity.
When to Use FromBody Attribute in ASP.NET Core?
In ASP.NET Core, the FromBody attribute binds action method parameters from the request
body. It’s especially useful when dealing with HTTP methods like POST, PUT, and PATCH,
where significant data is sent in the request body. Here’s when we would typically use the
FromBody attribute:
RESTful APIs:
When creating (POST) or updating (PUT, PATCH) resources, you often send the entire
resource or changes in the request body. The FromBody attribute allows the automatic
deserialization of the incoming request body to a .NET object.
[HttpPost]

public IActionResult Create([FromBody] Product product)

// Add product to database or process it

}
Complex Data Types:
For sending complex objects, arrays, or other structured data. Query strings (with
FromQuery) are better suited for simple values or filtering criteria, while the request body
can accommodate more structured and comprehensive data.
JSON or XML Payloads:
Modern web APIs often communicate using JSON or XML payloads. Clients send this data
in the request body, and the FromBody attribute tells the ASP.NET Core framework to use
input formatters to deserialize this data into the corresponding .NET types.
Avoiding Length Limitations:
URLs have length limits. When you have a large amount of data to send, sending it in the
body is more suitable than as a query string or as part of the URL.
40

Increased Data Privacy:


While both the body and URL are encrypted in HTTPS requests, data in the URL can be
stored in browser history, server logs, or analytics tools. It’s often better to send more
sensitive data in the body.
Working with Modern Frontend Frameworks:
If you’re building an application with modern frontend frameworks like React, Angular, or
Vue.js, sending data via the request body (often in JSON format) is the standard practice.
When Not to Use:
 Simple Data Types in GET Requests: For simple data types, especially in
GET requests, you’d use the query string or route data rather than the
request body.
 URL Identifiers: If you’re trying to capture resource identifiers or other data
that are part of the URL, you should use FromRoute or FromQuery.
Custom Model Binding in ASP.NET Core MVC
In this article, I will discuss Custom Model Binding in ASP.NET Core MVC with
Examples. Please read our previous article discussing Model Binding in ASP.NET Core
MVC with Complex Type.
Custom Model Binding in ASP.NET Core MVC
Custom model binding in ASP.NET Core MVC is the process of intercepting the standard
model binding process to provide custom logic for converting request data into action
method parameters. This is useful when you have unique data or formats that the built-in
model binders don’t support. To create a custom model binder, you typically follow these
steps:
 Create the Custom Model Binder: Implement the IModelBinder interface.
This interface requires you to implement the BindModelAsync method.
 Create a Model Binder Provider: Implement the IModelBinderProvider
interface. This helps ASP.NET decide when to use your custom binder.
 Register Your Custom Binder: Add your custom binder to the MVC options
during application startup.
Step-by-Step Implementation:
We want a custom binder that binds a comma-separated string from the query string to a list
of integers. Let us see how we can implement this in ASP.NET Core MVC.
Creating a Custom Model Binder:
Let us first create the Custom Model Binder. So, create a class file
named CommaSeparatedModelBinder.cs and then copy and paste the following code into
it. As you can see, this class implements the IModelBinder interface and provides an
implementation for the BindModelAsync method. As part of this method, we need to write
our custom logic. Here, we have written the logic that will bind a comma-separated string
from the query string to a list of integers. The following example code is self-explained, so
please go through the comment lines for a better understanding.

Traveling across Quang Binh to stunning cinematic locations00:14 / 03:1710 Sec

using Microsoft.AspNetCore.Mvc.ModelBinding;

using ModelBindingDemo.Models;
41

namespace ModelBindingDemo.Models

public class CommaSeparatedModelBinder : IModelBinder

//ModelBindingContext: A context that contains operating information for model binding and
validation.

public Task BindModelAsync(ModelBindingContext bindingContext)

//Get the Query String Parameter

var query = bindingContext.HttpContext.Request.Query;

//fetch the values based on the key

var Ids = query["Ids"].ToString();

//Check if the value is null or empty

if (string.IsNullOrEmpty(Ids))

return Task.CompletedTask;

//Splitting the comma separated values to list of integers

var values = Ids.Split(',').Select(int.Parse).ToList();

//Success: Creates a ModelBindingResult representing a successful model binding operation.

bindingContext.Result = ModelBindingResult.Success(values);

//Mark the Task Has Been completed successfully

return Task.CompletedTask;

}
42

}
Create a Model Binder Provider:
A provider allows more granular control, determining when your binder should be used
based on the context. So, create a class file
named CommaSeparatedModelBinderProvider.cs and copy and paste the following
code. In this case, we’re checking if the model type is a list of integers.
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace ModelBindingDemo.Models

public class CommaSeparatedModelBinderProvider : IModelBinderProvider

public IModelBinder GetBinder(ModelBinderProviderContext context)

//ModelType: Gets the model type represented by the current instance

if (context.Metadata.ModelType == typeof(List<int>))

return new CommaSeparatedModelBinder();

return null;

}
Registering the Custom Model Binder:
Register your custom model binder in the Startup.cs or Program.cs depending on your
ASP.NET Core version. As I am developing the application using the .NET 6 version, I will
register the custom model binder within the Main method of the Program class using the
code below.
builder.Services.AddControllersWithViews(options =>
43

// Add the provider at the top of the list to ensure it runs before default providers

options.ModelBinderProviders.Insert(0, new CommaSeparatedModelBinderProvider());

});
Using Custom Binder:
Now that everything’s set up, you can use your custom binder in your action methods. So,
modify the HomeController as follows to use Custom Model Binder.
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class HomeController : Controller

//[HttpGet("home/getdetails")]

//public IActionResult GetDetails([ModelBinder] List<int> Ids)

//{

// // Your logic here...

// return Ok(Ids);

//}

[HttpGet("home/getdetails")]

public IActionResult GetDetails([ModelBinder(typeof(CommaSeparatedModelBinder))]


List<int> Ids)

// Your logic...

return Ok(Ids);
44

}
When you access this action with a URL like /home/getdetails?Ids=1,2,3. The IDS
parameter will be a list containing the integers 1, 2, and 3.
So, custom model binding in ASP.NET Core provides flexibility in dealing with unique
request data scenarios. It allows you to seamlessly integrate custom parsing logic into the
framework’s model-binding process.
Custom Model Binding RealTime Example in ASP.NET Core MVC
Consider a real-time scenario for custom model binding in ASP.NET Core MVC: converting
a date range string in the format “startDate-endDate” into a custom DateRange object. This
is useful in reporting scenarios where users might provide a date range as part of their
query parameters.
Step 1: Define the DateRange Class
namespace ModelBindingDemo.Models

public class DateRange

public DateTime StartDate { get; set; }

public DateTime EndDate { get; set; }

}
Step 2: Implement the Custom Model Binder
using Microsoft.AspNetCore.Mvc.ModelBinding;

using System.Globalization;

namespace ModelBindingDemo.Models

public class DateRangeModelBinder : IModelBinder

public Task BindModelAsync(ModelBindingContext bindingContext)


45

CultureInfo provider = CultureInfo.InvariantCulture;

//Get the Query String Parameter

var query = bindingContext.HttpContext.Request.Query;

//fetch the values based on the key

var DateRangeQueryString = query["range"].ToString();

//Check if the value is null or empty

if (string.IsNullOrEmpty(DateRangeQueryString))

return Task.CompletedTask;

//Split the Values by -

var dateValues = DateRangeQueryString.Split('-');

if (dateValues.Length != 2)

bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid Date


Range Format.");

return Task.CompletedTask;

if (dateValues.Length == 2 && DateTime.TryParseExact(dateValues[0], "MM/dd/yyyy",


provider, DateTimeStyles.None, out DateTime startDate) &&
DateTime.TryParseExact(dateValues[1], "MM/dd/yyyy", provider, DateTimeStyles.None,
out DateTime endDate))

var dateRange = new DateRange { StartDate = startDate, EndDate = endDate };


46

bindingContext.Result = ModelBindingResult.Success(dateRange);

return Task.CompletedTask;

else

bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid Date


Range Format.");

return Task.CompletedTask;

}
Step3: Create a Model Binder Provider:
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace ModelBindingDemo.Models

public class DateRangeModelBinderProvider : IModelBinderProvider

public IModelBinder GetBinder(ModelBinderProviderContext context)

//ModelType: Gets the model type represented by the current instance

if (context.Metadata.ModelType == typeof(DateRange))

return new DateRangeModelBinder();


47

return null;

}
Step 3: Register the Custom Model Binder
builder.Services.AddControllersWithViews(options =>

// Add the provider at the top of the list to ensure it runs before default providers

options.ModelBinderProviders.Insert(0, new CommaSeparatedModelBinderProvider());

options.ModelBinderProviders.Insert(1, new DateRangeModelBinderProvider());

});
Step 4: Use the Custom Binder in an Action
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class HomeController : Controller

//[HttpGet("home/getdata")]

//public IActionResult GetData([ModelBinder] DateRange range)

//{

// // Do something with range.StartDate and range.EndDate

// return Ok($"From {range.StartDate} to {range.EndDate}");

//}
48

[HttpGet("home/getdata")]

public IActionResult GetData([ModelBinder(typeof(DateRangeModelBinder))] DateRange


range)

// Do something with range.StartDate and range.EndDate

return Ok($"From {range.StartDate} to {range.EndDate}");

}
When you access this action with a URL like /home/getdata?range=01/01/2023-
12/31/2023, the range parameter will be populated with the correct start and end dates. If
the format is incorrect, a model state error is added.
This real-time example showcases how custom model binding can deal with specific
formats or data types that the default model binders might not handle.
Complex Object Creation using Custom Model Binder in ASP.NET Core
MVC
Creating complex objects using custom model binding in ASP.NET Core MVC can be
beneficial in scenarios where the default model binders aren’t sufficient to generate an
object based on a combination of different parts of an HTTP request (e.g., headers, query
parameters, route values, and body data).
Suppose we have an object, ComplexUser, that we want to create based on data from the
request header, route, and query string.
1. Define the ComplexUser Class:
namespace ModelBindingDemo.Models

public class ComplexUser

public string Username { get; set; }

public int Age { get; set; }

public string Country { get; set; }

public string ReferenceId { get; set; }


49

}
Where:
 Username comes from a header named “X-Username”.
 Age is derived from the query string.
 Country is part of the route.
 ReferenceId is also from the query string.
2. Implement the Custom Model Binder:
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace ModelBindingDemo.Models

public class ComplexUserModelBinder : IModelBinder

public Task BindModelAsync(ModelBindingContext bindingContext)

var headers = bindingContext.HttpContext.Request.Headers;

var routeData = bindingContext.HttpContext.Request.RouteValues;

var query = bindingContext.HttpContext.Request.Query;

var user = new ComplexUser

Username = headers["X-Username"].ToString(),

Country = routeData["country"].ToString(),

Age = int.TryParse(query["age"].ToString(), out var age) ? age : 0,

ReferenceId = query["refId"].ToString()

};

bindingContext.Result = ModelBindingResult.Success(user);

return Task.CompletedTask;
50

}
Step3: Create a Model Binder Provider:
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace ModelBindingDemo.Models

public class ComplexUserModelBinderProvider : IModelBinderProvider

public IModelBinder GetBinder(ModelBinderProviderContext context)

//ModelType: Gets the model type represented by the current instance

if (context.Metadata.ModelType == typeof(ComplexUser))

return new ComplexUserModelBinder();

return null;

}
3. Register the Custom Model Binder:
In the Startup.cs or Program.cs (depending on your ASP.NET Core version):
builder.Services.AddControllersWithViews(options =>

// Add the provider at the top of the list to ensure it runs before default providers
51

options.ModelBinderProviders.Insert(0, new CommaSeparatedModelBinderProvider());

options.ModelBinderProviders.Insert(1, new DateRangeModelBinderProvider());

options.ModelBinderProviders.Insert(2, new ComplexUserModelBinderProvider());

});
4. Use the Custom Binder in an Action:
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class HomeController : Controller

[HttpGet("data/{country}")]

public IActionResult
GetComplexUserData([ModelBinder(typeof(ComplexUserModelBinder))] ComplexUser
user)

// Your logic...

return Ok(user);

}
With this setup, if you send a request to /data/India?age=25&refId=12345 with the header
X-Username: Pranaya, the ComplexUser parameter in the action will be populated based
on the custom binding logic.
Single Property Binding in ASP.NET Core MVC
We can also achieve the previous example using Single Property Binding in ASP.NET Core
MVC. In ASP.NET Core MVC, single property binding refers to the capability to bind a
52

specific property of an object differently from other properties. This can be achieved by
using model binding attributes on individual properties.
We can use the built-in attributes like [FromRoute], [FromQuery], [FromBody], [FromForm],
and [FromHeader] on individual properties to specify where the property value should come
from. So, modify the ComplexUser class as follows.
using Microsoft.AspNetCore.Mvc;

namespace ModelBindingDemo.Models

public class ComplexUser

[FromHeader(Name = "X-Username")]

public string? Username { get; set; }

[FromQuery(Name = "age")]

public int Age { get; set; }

[FromRoute(Name = "country")]

public string? Country { get; set; }

[FromQuery(Name = "refid")]

public string? ReferenceId { get; set; }

}
With the above changes in place, modify the Home Controller as follows:
using Microsoft.AspNetCore.Mvc;

using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers

public class HomeController : Controller

{
53

[HttpGet("data/{country}")]

public IActionResult GetComplexUserData(ComplexUser user)

// Your logic...

return Ok(user);

Data Annotations in ASP.NET Core MVC


In this article, I will briefly introduce Data Annotations in ASP.NET Core
MVC Applications. Please read our previous article discussing Model Binding in ASP.NET
Core MVC Applications.
Data Annotations in ASP.NET Core MVC
In ASP.NET Core MVC, Data Annotations are commonly used to control the behavior of
model classes when they interact with Views, Databases, and Validation Processes. Data
Annotations are Attributes (i.e., classes inherited from the Attribute class) that we can apply
to our model properties to specify how they should be treated in various situations. These
data annotations are primarily used in Entity Framework Core, ASP.NET Core MVC,
ASP.NET Core Web API, and ASP.NET Core Razor Pages Applications.
So, the first important point that you need to remember is that Data Annotations in
ASP.NET Core are not only used for Validation Purposes but also when we are working
with Entity Framework Core to create the database based on our model and when we want
how the model properties should display in a Razor View.
Note: We need to include System.ComponentModel.DataAnnotations namespace,
which provides the Data Annotations Attributes.

Traveling across Quang Binh to stunning cinematic locations00:07 / 03:1710 Sec

When Should We Use Data Annotations in ASP.NET Core MVC?


The following are some of the specific scenarios where we can use Data Annotation
Attributes in ASP.NET Core:
Model Validation:
Data Annotations provide a straightforward way to implement validation logic on model
properties. These annotations provide validations for model properties such as required
fields, allowed range of values, and proper formatting of input data. The following are the
most commonly used Validation Attributes in ASP.NET Core Applications:
 [Required]: This attribute is used to mark a property as mandatory.
54

 [MinLength(length)] and [MaxLength(length)]: These attributes specify the


maximum and minimum lengths for a string property, respectively.

 [StringLength(maxLength, MinimumLength = minLength)]: This attribute


sets the maximum length of a string property and optional minimum length. It
is commonly used to validate input data such as names, descriptions, etc. For
example, [StringLength(100, MinimumLength = 10)] requires the string to be
between 10 and 100 characters long.
 [Range(min, max)]: This attribute restricts a property to a certain range of
values. It is applicable to numerical data where you need to set minimum and
maximum values. For example, [Range(1, 100)] indicates that the value must
be between 1 and 100.

 [EmailAddress]: Checks that a property has a valid email format.


 [RegularExpression(pattern)]: This attribute ensures that the property value
matches a specified regular expression, which is useful for validating formats
like phone numbers, zip codes, etc. For example, [RegularExpression(@”^\
d{5}(-\d{4})?$”, ErrorMessage = “Invalid ZIP Code”)].
 [Compare(“OtherProperty”)]: This attribute is used to compare two
properties of a model, often used to confirm passwords, emails, account
numbers, and other fields that require verification. For example,
[Compare(“Password”, ErrorMessage = “The password and confirmation
password do not match.”)].
 [DataType(type)]: This attribute specifies the type of data, such as Date,
Time, Email Address, etc. It can help choose the right input field and can also
provide client-side validation.
 [Phone]: This is one of the enum values of the DataType enum, which
checks that the property value is a valid phone number format.
 [Url]: This is one of the enum values of the DataType enum, which checks
that the property value is a valid URL format.
 [CreditCard]: This is one of the enum values of the DataType enum, which
checks that the property value is a valid credit card format, including checking
the card number against known standards.
User Interface Configuration:
The Data Annotation Attributes also allow for the customization of display labels, error
messages, and even descriptions for UI elements. The following are the Attributes that fall
under this category.
 [Display(Name = “Display Name”)]: This attribute is used to specify the text
that should be used when rendering labels for properties in forms. For
example, [Display(Name = “Full Name”)] specifies what label to use for a
given property in forms.
 [DisplayOrder(order)]: Used to specify the order in which fields are
displayed.
 [DisplayName(“Name”)]: Similar to Display, but it provides a simpler way to
specify just the name.
Data Formatting:
The Data Annotations Attributes in ASP.NET Core can also specify how data should be
displayed or formatted without affecting the underlying database schema. For example, the
55

[DisplayFormat] attribute can specify how date fields are displayed in your UI, such as
showing only the date part of a DateTime object.
 [DisplayFormat(DataFormatString = “{0:F2}”)]: Defines the display format
for a property, useful for formatting dates, numbers, etc.
Client-Side Validation Enhancement:
While data annotations primarily enforce server-side validation, they can also automatically
integrate with client-side validation when using ASP.NET Core MVC. Data Annotations
Attributes like [Required], [StringLength(maxLength)], [Range(minimum, maximum)],
[Compare(otherPropertyName)], [RegularExpression(pattern)], [EmailAddress], [Phone],
[Url], [CreditCard] translate directly to HTML5 data val attributes that work with jQuery
Validation plugins.
Database Schema Configuration:
The Data Annotations Attributes can also specify how the model properties map to
database columns when using the Entity Framework Core Code First Approach. The
following are a few of such Attributes.
 [Key]: Used to denote a property as the primary key in a model.
 [ForeignKey(“RelatedEntity”)]: Used in Entity Framework to indicate that a
property is a foreign key.
 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]: Specifies how
the database generates values for a property.
 [Column(“ColumnNameInDb”)]: Specifies a column name in the database
that is different from the property name in the model.
 [Index]: Attributes like [Index] can be used to define indexing behavior on
specific columns to improve database performance.
Model Binding Attributes:
Data Annotation Attributes in ASP.NET Core can also manage how model binding should
treat certain properties, such as excluding a property from model binding and specifying that
a Property should be present to perform Model Binding. The following two Attributes fall into
this category:
 [BindNever]: It indicates that a model property should not be bound by
model binding. This can be useful for properties that should not be updated
directly from the data in the request, such as IDs, timestamps, or user-
sensitive data that needs to be protected or generated by the system.
 [BindRequired]: It indicates that a model property should be present in the
incoming data for the model binding to succeed. If the field is missing during
model binding, a model state error is added.
Limitations of Data Annotations in ASP.NET Core:
There are scenarios where data annotations might not be the best choice:
 Complex Validation Rules: For validation logic that is too complex to be
expressed in data annotations (e.g., cross-field validation, dependency on
external systems), Fluent Validation or custom validation methods should be
considered.
 Separation of Concerns: If you prefer a strict separation of concerns where
your domain model does not contain any hints about presentation or
persistence, you might opt for a separate configuration class or use other
validation frameworks.
56

Model Validations in ASP.NET Core MVC


In this article, I will discuss Model Validations in ASP.NET Core MVC with Examples.
Please read our previous article discussing Data Annotations in ASP.NET Core MVC.
What are Validations?
Validations in a web application refer to the processes and techniques used to ensure that
data entered by users into web forms is accurate, secure, and appropriate before being
processed or stored in the database. The main purpose of validations is to maintain data
integrity, enhance user experience, and secure the website against malicious inputs. There
are three types of validations:
 Server-Side Validations
 Client-Side Validations
 Database Validations
Client-Side Validation:
This type of validation is performed on the user’s device, typically within the browser. It uses
JavaScript or similar technologies (jQuery) to validate data as the user fills out the form.
This is useful for providing immediate feedback to users about the validity of their data (like
formatting errors in email addresses or passwords). However, we cannot solely depend
upon it since the client-side validations can be bypassed if a user disables JavaScript or
manipulates the client-side code. The following are the key points:
 It occurs in the user’s browser.
 Uses JavaScript (or similar client-side scripting languages) to validate data
before it’s sent to the server.
 It provides a responsive user experience because validation feedback is
immediate, without requiring a round trip to the server.
 We should never solely depend on client-side validations since users can
bypass or manipulate client-side scripts.
Server-Side Validation:
This validation occurs once the data has been submitted to the server. It is important for
security and data integrity because it checks the data against a broader range of criteria
and rules that end users can’t easily bypass. This includes checking the uniqueness of a
username, verifying user credentials, or performing more complex checks such as
compliance with business rules. The following are the key points:
Ad
1/2
00:25
57

Traveling across Quang Binh to stunning cinematic locations


 It occurs on the web server after the data is sent from the client’s browser.
 Uses server-side scripting languages (like C#, Java, Python, PHP, etc.) to
validate the data before processing it or saving it to a database.
 While client-side validation can improve user experience by providing
immediate feedback, it can be bypassed or disabled. Server-side validation
acts as a necessary fallback to ensure data correctness and security.
 It helps prevent common attacks such as SQL injection, cross-site scripting
(XSS), and other forms of data tampering. By validating the inputs, the server
ensures that executable code or malicious scripts are not injected into the
database or displayed to other users.
Database Validation:
This involves setting constraints in the database that prevent invalid data from being stored.
For example, setting a column as unique to avoid duplicate entries or defining a foreign key
relationship to ensure consistency across tables. Database validations are the last line of
defense for data integrity.
Why do we Need Data Annotation Attributes in ASP.NET Core MVC for
Validation?
Nowadays, it’s challenging for a web developer to validate user input for any Web
application. As web developers, we not only validate the business data on the client side,
i.e., in the browser, but we also need to validate the business data on the server side. That
means we need to validate the business data on the client and server sides.
Client-side validation gives users immediate feedback on the information they enter into a
web page, which is an expected feature in today’s web applications. Along the same line,
server-side validation is in place because we never trust the information coming over the
network or from the client.
Data Annotations in ASP.NET Core MVC support both Client-Side and Server-Side
Validation. In addition to Data Annotation, we can also use Fluent API Validations, which
gives us more control than Data Annotation.
Common Validation Scenarios Encountered on Websites:
The following are the common validation scenarios encountered in a website.
 Required Fields: Ensuring that mandatory fields are filled in before form
submission.
 Data Type Validation: Verify that the data entered is of the correct type, e.g.,
numbers, text, and dates.
 Range Validation: Checking if a numeric value falls within a certain range.
For instance, age should be between 18 and 60.
 Format Validation: Verifying if data matches a particular format, e.g., email
addresses, phone numbers, and postal codes.
 Consistency Check: Ensuring that data across multiple fields is consistent.
For instance, the “Confirm Password” field should match the “Password” field.
Another example you can take when entering the CreditCard Number.
 Custom Business Rules: Certain validations might be unique depending on
the application’s business logic. For example, a flight booking system might
validate that the return date is after the departure date.
58

 Blacklist Checks: This involves checking inputs against known malicious or


undesired inputs. It’s a basic defense against code injection attacks. Suppose
you want to restrict some of the characters entered on a field.
 Whitelist Checks: Only allow a specific set of values or patterns for input. It’s
generally more secure than blacklist checks because it’s restrictive. It is just
the opposite of Blacklist Checks.
 Size Limit Checks: This ensures that data, especially uploaded files, don’t
exceed a certain size. When entering an image, you need to set its size.
 CAPTCHAs: To validate that the user is a human, not a bot.
Built-in Data Annotations Attributes for Model Validation in ASP.NET
Core MVC:
The System.ComponentModel.DataAnnotations assembly has many built-in validation
attributes that can be used for validations. They are as follows:
 [Required]: This attribute is used to mark a property as mandatory. The MVC
framework will not consider a model valid unless this property has been
supplied.
 [MinLength(length)] and [MaxLength(length)]: These attributes specify the
maximum and minimum lengths for a string property, respectively.
 [StringLength(maxLength, MinimumLength = minLength)]: This attribute
sets the maximum length of a string property and optional minimum length. It
is commonly used to validate input data such as names, descriptions, etc.
 [Range(min, max)]: This attribute restricts a property to a certain range of
values. It is applicable to numerical data where you need to set minimum and
maximum values.
 [EmailAddress]: Checks that a property has a valid email format.
 [RegularExpression(pattern)]: This attribute ensures that the property value
matches a specified regular expression, which is useful for validating formats
like phone numbers, zip codes, etc.
 [Compare(“OtherProperty”)]: This attribute is used to compare two
properties of a model, often used to confirm passwords, emails, and other
fields that require verification.
 [DataType(type)]: This attribute specifies the type of data, such as Date,
Time, Email Address, URL, Credit Card, etc. It can help choose the right input
field and can also assist in client-side validation.
Example to Understand Model Validation in ASP.NET Core MVC
Let’s understand Model Validation in an ASP.NET Core MVC Application using Data
Annotations with one example. We want to create the following Employee Form.
59

We want to make the Name, Email, and Department fields required. If the required values
are not provided and the form is submitted, we want to display the required validation
errors, as shown in the image below.
60

If we enter an Invalid Email, it should display an Invalid Email Format validation error, as
shown below.

Let us proceed and see how we can implement this using Data Annotation Attributes in
ASP.NET Core MVC Application.
Creating the Model with Data Annotation Attributes:
Create a class file named Department.cs, then copy and paste the following code. As you
can see, this will be an Enum, and these Enum constants will be the data source for our
department field.
namespace DataAnnotationsDemo.Models

public enum Department

None,

HR,

Payroll,

IT
61

}
Next, create another class file named Employee.cs and copy and paste the following code.
As you can see in the code below, we have marked the Name, Email, and Department
properties with the Required Attribute, which makes these properties required while
performing the model validation. The Required Data Annotation Attribute belongs to
the System.ComponentModel.DataAnnotations namespace.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

public int Id { get; set; }

[Required]

public string Name { get; set; }

[Required]

public string Email { get; set; }

[Required]

public Department? Department { get; set; }

}
Creating Employee Controller:
Create a new Empty – MVC Controller with the name EmployeeController and then copy
and paste the following code into it.
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

{
62

public class EmployeeController : Controller

public IActionResult Create()

return View();

[HttpPost]

public IActionResult Create(Employee employee)

//Check if the Model State is Valid

if (ModelState.IsValid)

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");

//Return to the same View and Display Model Validation error

return View();

public string Successful()

return "Employee Addedd Successfully";

}
63

}
Explanation:
 In our example, the Create() action method, decorated with HTTP Post
Attribute, will be executed when the form is submitted.
 The model for the Create Employee Form is the Employee class. When the
form is submitted, model binding maps the posted form values to the
respective properties of the Employee class.
 With the Required attribute on the Name, Email, and Department properties
of the Employee class, if a value for these properties is not present, the
model validation fails.
 We need to use ModelState.IsValid property to check if validation has failed
or succeeded. If validation has failed, we return the same view so the user
can provide the required data and resubmit the form. If there is no validation
error, we often save the data into the database and then redirect to a different
view as per the business requirement.
What is ModelState.IsValid in ASP.NET Core MVC?
ModelState.IsValid in ASP.NET Core MVC is a property that indicates whether the data
posted to a server is valid according to the data annotations and validation rules set on the
server-side models. This property in controller actions ensures that the data sent by the
client is valid. Here is how ModelState.IsValid works:
 Data Annotation Validation: ASP.NET Core models can be decorated with
data annotations that define validation rules. For example, you can use
annotations like [Required], [StringLength], [Range], etc to provide the
Validation Rules for a Model property.
 Automatic Validation Check: When a request is made to an ASP.NET Core
controller action, the framework automatically binds incoming request data to
action parameters and checks these against the model’s validation rules. This
process populates the ModelState.
 ModelState Dictionary: The ModelState is a property of the controller base
class, represented as a dictionary containing the model’s state and the
validation errors. It includes information about each field (like whether there
were any errors during conversion, formatting, or validation based on the
rules).
 Checking ModelState.IsValid: In the controller actions, we need to check
the ModelState.IsValid property to determine if the data passed to the action
meets all the validation criteria defined by the model. If ModelState.IsValid
returns false, which means that at least one of the validation rules was not
satisfied.
 Handling Validation Errors: If ModelState.IsValid is false. We typically
need to return to the same view in which the user entered data and display
the validation error messages stored in the ModelState. This helps the user
correct their input data and resubmit the form.
Displaying Model Validation Error in ASP.NET Core MVC Razor Views:
We need to use asp-validation-for and asp-validation-summary tag helpers to display
model validation errors in Views. The asp-validation-for tag helper displays a validation
message for a single property of our model class. On the other hand, the asp-validation-
summary tag helper summarizes all validation errors that occurred on a model.
64

For example, to display the validation error associated with the Name property of the
Employee class, we need to use asp-validation-for tag helper on a <span> element, as
shown below.
<div class="form-group row">

<label asp-for="Name" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Name" class="form-control" placeholder="Enter Your Name">

<span asp-validation-for="Name"></span>

</div>

</div>
On the other hand, to display a summary of all validation errors, we need to use the asp-
validation-summary tag helper on a <div> element, as shown below.
<div asp-validation-summary="All" class="text-danger"></div>
The value for the asp-validation-summary tag helper can be any of the following.
 All: When you set the asp-validation-summary attribute to All, it displays all
validation messages. This includes messages that are associated with model
properties as well as messages that are added to the ModelState without a
key (often used for summary error messages that aren’t tied to a specific
field).
 ModelOnly: Setting the asp-validation-summary attribute to ModelOnly will
display only those validation messages that are directly associated with the
model properties. This setting excludes any error messages that are added to
the ModelState without a specific key.
 None: When the asp-validation-summary is set to None, no validation
summary is displayed. This option effectively disables the validation summary
display.
Let us use All. By default, it will show the error message in black color. If you want to
display the error message in red, use the bootstrap class=”text-danger” as follows.
<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group row">

<label asp-for="Name" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Name" class="form-control" placeholder="Enter Your Name">

<span asp-validation-for="Name" class="text-danger"></span>


65

</div>

</div>
Creating ASP.NET Core Razor Views to Display Error Messages:
Next, create a view for the Create action method of Employee Controller. Once you add
the Create.cshtml view file, please copy and paste the following code.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group row">

<label asp-for="Name" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Name" class="form-control" placeholder="Enter Your Name">

<span asp-validation-for="Name" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Email" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Email" class="form-control" placeholder="Enter Your Email">

<span asp-validation-for="Email" class="text-danger"></span>


66

</div>

</div>

<div class="form-group row">

<label asp-for="Department" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<select asp-for="Department" class="form-select"

asp-items="Html.GetEnumSelectList<Department>()">

<option value="">Please Select</option>

</select>

<span asp-validation-for="Department" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
Now, run the application, navigate to /Employee/Create URL, and submit the form without
filling in any data, and you will see the following error messages.
67

Customizing Model Validation Error Message in ASP.NET Core MVC


By default, the Required attribute on the Name, Email, and Department properties displays
the following default validation error messages.
 The Name field is required.
 The Email field is required.
 The Department field is required.
If you want to customize the validation error message, use the ErrorMessage property of
the Required attribute. Modify the Employee model class, as shown below, to display a
user-friendly error message when the user submits the form without providing values for
these fields.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

{
68

public int Id { get; set; }

[Required(ErrorMessage = "Please Enter the First Name")]

public string Name { get; set; }

[Required(ErrorMessage = "Please Enter the Email Address")]

public string Email { get; set; }

[Required(ErrorMessage = "Please Select the Department")]

public Department? Department { get; set; }

}
With the above model changes, run the application, navigate to
the /Employee/Create URL, and submit the form without filling in any data. You will see the
following custom error messages as expected.

Using Multiple Validation Attributes on a Single Model Property:


69

It is also possible in ASP.NET Core to apply multiple validation attributes on a single


property. For example, we have only applied the Required Attribute with the Email Property.
So, in this case, it will only check whether you are providing any value for the Email Field. It
is not going to check whether the entered value is in the proper format. So, if you enter
anything and submit the form, it will not give you any error message, as shown in the below
image.

Along with the Required Validation, we want to apply the Invalid Format error message for
the Email ID field. To achieve this, we must use either the EmailAddress Attribute
or RegularExpression attribute along with the Required Attribute. So, modify the
Employee class as follows.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

public int Id { get; set; }


70

[Required(ErrorMessage = "Please Enter the First Name")]

public string Name { get; set; }

[Required(ErrorMessage = "Please Enter the Email Address")]

//[EmailAddress(ErrorMessage = "Please Enter a Valid Email")]

[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",

ErrorMessage = "Please Enter a Valid Email")]

public string Email { get; set; }

[Required(ErrorMessage = "Please Select the Department")]

public Department? Department { get; set; }

}
With the above changes in place, run the application, navigate to
the /Employee/Create URL, and submit the form by entering an invalid email address. You
should get the appropriate error message, as shown in the below image.
71

Currently, Validation is performed on the server side, not on the client side. The Validation
attributes in ASP.NET Core MVC Framework provide both client-side and server-side
validation. Let us proceed to understand how to enable client-side validation in ASP.NET
Core MVC Application.
Enabling Client-Side Validation in ASP.NET Core MVC:
Client-Side Validation in ASP.NET Core MVC Application is a feature that allows us to
perform validation on the client browser without making a round trip to the server. This
provides a more responsive experience for the user. ASP.NET Core MVC uses jQuery
Validation and jQuery Unobtrusive Validation for client-side validation. Here’s how you can
enable client-side validation in an ASP.NET Core MVC application:
You must include jQuery, jQuery Validation, and jQuery Unobtrusive Validation in your
project. These can be added via CDNs or local files. For example, include them via CDNs in
your _Layout.cshtml:
<!-- jQuery -->

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<!-- jQuery Validation Plugin -->

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.5/
jquery.validate.min.js"></script>
72

!-- jQuery Unobtrusive Validation (to bridge ASP.NET Core MVC validation with jQuery
Validate) -->

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.13/
jquery.validate.unobtrusive.min.js">
Please add the following files in your _Layout.cshtml to enable client-side validation using
local files. The order is also important, and please include them in the following order.
<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
The order in which the script files are referenced is also important. This is
because jquery.validate is dependent on jquery, and jquery.validate.unobtrusive is
dependent on jquery.validate. So, they should be referenced in the above order.
Otherwise, client-side validation will not work as expected. In short, JavaScript is parsed
“top-down”, so all dependencies must be referenced before the dependent reference.
With these changes in place, the client-side validation should be active in your ASP.NET
Core MVC application. Whenever a user attempts to submit the form, jQuery Validation will
check input values against the data annotations before the form is submitted. If validation
fails, appropriate error messages will be displayed next to the form fields.
ASP.NET Core MVC uses these libraries to enhance and streamline client-side scripting
and validation:
 jQuery: It provides core functionality for DOM manipulation and event
handling, making it simpler to write complex client-side scripts that enhance
user experience.
 jQuery Validation: Integrated with ASP.NET Core MVC’s server-side
validation, this plugin ensures that validation rules are enforced on the client
side before the form data is sent to the server. This reduces the server load
and provides immediate feedback to users.
 jQuery Unobtrusive Validation: Works with the jQuery Validation plugin and
ASP.NET Core MVC’s validation annotations. It uses data-* attributes
generated from model annotations to control the behavior of client-side
validation without additional scripting. This helps keep your JavaScript code
minimal and separated from HTML content.
Best Practices to Use Validation Related JavaScript files:
We should not include the following three JavaScript files in our Layout File to enable
Client-Side Validation using Data Annotation.
<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
73

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
You need to remember that we are not going to create forms or perform client-side
validations on every web page of our application. If we include the above JavaScript files
inside our Layout files, they will be downloaded on every page where we use the layout file,
even if we are not performing client-side validations.
Creating a Partial View:
So, to overcome this problem, let us create a partial view with the
name _ValidationScriptsPartial.cshtml within our application’s Views/Shared folder and
then copy and paste the following code into it. We are not including the jquery.js file
because the jQuery.js file is required in all the pages, and hence, it is better to put that file
inside the layout file.
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
Next, we must add a render section inside the layout file to include the partial view above.
So, add the following code just before the end of the body tag of the layout file.
@await RenderSectionAsync("Scripts", required: false)
This is where we need to add the validation script. Suppose our Create.cshtml view wants
client-side validation; then, we need to add the following code just before the end of the
view.
@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
With these changes in place, run the application, and you will see that the client-side
validation is working as expected.
Using HTML Helper to Provide Validation Message:
Instead of using asp-validation-summary and asp-validation-for tag helpers, we can also
use Html.ValidationSummary and Html.ValidationMessageFor html helper methods to
achieve the same. So, modify the Create.cshtml file as shown below to use HTML helper
methods to display the error message.
@model DataAnnotationsDemo.Models.Employee

@{
74

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

@*<div asp-validation-summary="All" class="text-danger"></div>*@

<div class="text-danger">

@Html.ValidationSummary()

</div>

<div class="form-group row">

<label asp-for="Name" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Name" class="form-control" placeholder="Enter Your Name">

@* <span asp-validation-for="Name" class="text-danger"></span>*@

<span class="text-danger">

@Html.ValidationMessageFor(x => x.Name)

</span>

</div>

</div>

<div class="form-group row">

<label asp-for="Email" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Email" class="form-control" placeholder="Enter Your Email">

@* <span asp-validation-for="Email" class="text-danger"></span>*@


75

<span class="text-danger">

@Html.ValidationMessageFor(x => x.Email)

</span>

</div>

</div>

<div class="form-group row">

<label asp-for="Department" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<select asp-for="Department" class="form-select"

asp-items="Html.GetEnumSelectList<Department>()">

<option value="">Please Select</option>

</select>

@*<span asp-validation-for="Department" class="text-danger"></span>*@

<span class="text-danger">

@Html.ValidationMessageFor(x => x.Department)

</span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>
76

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
Which Data Annotation Attributes support Client Side Validations in
ASP.NET Core MVC?
All Data Annotation Attributes are not supported by client-side validation. The following are
the data annotation attributes that support client-side validation in ASP.NET Core MVC:
 [Required]
 [StringLength(maxLength)]
 [Range(minimum, maximum)]
 [Compare(otherPropertyName)]
 [RegularExpression(pattern)]
 [EmailAddress]
 [Phone]
 [Url]
 [CreditCard]
Best Practices to Implement Validation in a Website:
 Combine with Client-side Validation: Use server-side and client-side
validation for better security and user experience.
 Consistent Validation Across Client and Server: Ensure that client-side
and server-side validations follow the same rules for consistency and
redundancy.
 User Feedback: Provide clear, helpful error messages for the end users
when their inputs fail server-side validation.

Data Annotation Attributes in ASP.NET Core MVC


In this article, I will discuss How to Perform Model Validations using Data Annotation
Attributes in the ASP.NET Core MVC Application with Examples. Please read our
previous article discussing Model Validations in ASP.NET Core MVC.
Built-in Data Annotation Attributes
ASP.NET Core MVC Provides many built-in Data Annotation Attributes for Model Validation
as follows. These Data Annotation Attributes belong to
the System.ComponentModel.DataAnnotations namespace.
 [Required]: The Required attribute is used to mark a property as mandatory.
 [MinLength(length)] and [MaxLength(length)]: These two Data Annotation
Attributes specify the maximum and minimum lengths for a string property,
respectively.
77

 [StringLength(maxLength, MinimumLength = minLength)]: This Attribute


sets the maximum length of a string property and optional minimum length.
 [Range(min, max)]: This attribute restricts a property to a certain range of
values. It is applicable to numerical data where you need to set minimum and
maximum values.
 [EmailAddress]: This attribute checks a property has a valid email format.
 [RegularExpression(pattern)]: This attribute ensures that the property value
matches a specified regular expression, which is useful for validating formats
like phone numbers, zip codes, email formats, etc.
 [Compare(“OtherProperty”)]: This attribute compares two properties of a
model. It is often used to confirm passwords, emails, and other fields that
require verification.
 [DataType(type)]: This Data Annotation Attribute specifies the type of data,
such as Date, URL, Phone, CreditCard, Time, Email Address, etc.
Let us understand the above Attributes by taking one real-time example. At the end of this
article, we will create the following Employee Creation Form with Proper Validation both at
the Client and Server Side using the Built-in Data Annotation Attributes of ASP.NET Core
MVC Framework.

NextStayTraveling across Quang Binh to stunning cinematic locations00:49 / 03:1710


Sec
78

Required Data Annotation Attribute in ASP.NET Core MVC


Our business requirement is that an employee’s First Name and Last Name can’t be empty.
That means we will force the user to give the first and last names. In the ASP.NET Core
MVC application, we can achieve this very easily by decorating
the FirstName and LastName model properties with the Required Data Annotation
attribute. The Required attribute makes the model property as required. For a better
understanding, please look at the following Employee Model:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[Required]

public string FirstName { get; set; }

[Required]

public string LastName { get; set; }

}
Using the Model Inside a Controller:
Let us use the Employee model inside a Controller. So, modify the Employee Controller as
follows. The GET version of the Create action method will render the view where it will ask
the user to submit the form with First and Last Name values. Once the form is submitted, it
will submit the form data to the Post version of the Create action method. Here, we are
using ModelState.IsValid property to check whether the Model state is valid or not. If valid,
we process the request and navigate to the Successful page, and if not, then we stay in the
same view by displaying the Model Validation error messages.
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class EmployeeController : Controller


79

public IActionResult Create()

return View();

[HttpPost]

public IActionResult Create(Employee employee)

//Check if the Model State is Valid

if (ModelState.IsValid)

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");

//Return to the same View and Display Model Validation error

return View();

public string Successful()

return "Employee Added Successfully";

}
80

Using the Model Inside a View with Validation:


Let us use the Employee model inside the Create.cshtml view. So, modify the Create.cshtml
view as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

@*<div asp-validation-summary="All" class="text-danger"></div>*@

<div class="form-group row">

<label asp-for="FirstName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="FirstName" class="form-control" placeholder="Enter Your First Name">

<span asp-validation-for="FirstName" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="LastName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="LastName" class="form-control" placeholder="Enter Your Last Name">

<span asp-validation-for="LastName" class="text-danger"></span>

</div>

</div>
81

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
The Required attribute raises a validation error if the property value is null or empty. When
we submit the page without providing an employee’s first and last name, we will get the
error message as shown in the image below.

With the Required Data Annotation attributes in place, if someone tries to submit the page
without providing the FirstName and LastName values, it will give us the default error, as
shown in the above image. But suppose you want to provide some user-defined error
message when validation fails. In that case, you can use the other overloaded version of
the Required attribute, which accepts ErrorMessage as an input parameter. For a better
understanding, please modify the Employee class as follows:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

{
82

[Required(ErrorMessage = "First Name is Required")]

public string FirstName { get; set; }

[Required(ErrorMessage = "Last Name is Required")]

public string LastName { get; set; }

}
With the above changes, if someone tries to submit the page without providing the
FirstName and LastName values, it will give the user-defined error message, as shown in
the image below.

StringLength Data Annotation Attribute in ASP.NET Core MVC


In our last example, we are forcing the user to enter his first and last names, but what
happens if he enters an enormously long name? For example, our business requirement is
that the employee’s last name should not be more than 30 characters, which means we
need to set a maximum of 30 characters that can be entered for the employee’s last name.
We can easily achieve this using the StringLength data annotation attribute in the
ASP.NET Core MVC application. To achieve this, we must decorate the LastName
property with the StringLength attribute, as shown below.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

{
83

[Required(ErrorMessage = "First Name is Required")]

public string FirstName { get; set; }

[Required(ErrorMessage = "Last Name is Required")]

[StringLength(30)]

public string LastName { get; set; }

}
With the above changes in place, you cannot enter more than 30 characters in the Last
Name text box, as shown in the image below.

The [StringLength] attribute verifies that a string is of a certain length but does not enforce
that the property is REQUIRED. If you want to enforce the property as required, use
the [Required] attribute and the [StringLength] attribute. That means it is also possible to
apply multiple validation attributes on a single property.
The MinimumLength is an optional named parameter used to specify a string’s minimum
length. We can also specify the user-defined error message. Here, we specify the minimum
length as 4 and the ErrorMessage as the Last name should be between 4 and 30
characters.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee


84

[Required(ErrorMessage = "First Name is Required")]

public string FirstName { get; set; }

[Required(ErrorMessage = "Last Name is Required")]

[StringLength(30, MinimumLength = 4, ErrorMessage = "Last name should be between 4


and 30 characters")]

public string LastName { get; set; }

}
In the above example, we have decorated the LastName property with
the StringLength attribute and then specified the Minimum and Maximum length of the
model properties. We also used the [Required] attribute. So, at this point, the LastName
property is required and should be between 4 and 30 characters. Now run the application
and check everything is working as expected, as shown below.

RegularExpression Data Annotation Attribute in ASP.NET Core MVC:


The Regular Expression Attribute is generally used for pattern-matching validations in
ASP.NET Core MVC applications. Let’s understand the Regular expression attribute with an
example. Suppose we need to validate the Email ID of an employee; then, we can achieve
this very easily in the ASP.NET MVC application by using Regular expression attributes.
So, modify the Employee model as follows:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

{
85

public class Employee

[Required(ErrorMessage = "Email id is required")]

[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",

ErrorMessage = "Please Enter a Valid Email")]

public string EmailId { get; set; }

}
Next, modify the Create.cshtml view as follows.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

@*<div asp-validation-summary="All" class="text-danger"></div>*@

<div class="form-group row">

<label asp-for="EmailId" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="EmailId" class="form-control" placeholder="Enter Your Email Id">

<span asp-validation-for="EmailId" class="text-danger"></span>

</div>

</div>
86

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
In the above example, we are applying both Required and RegularExpression attributes to
the EmailID model property, which ensures that the Email ID field is a required field and will
validate whether the field value is a proper email ID format or not, as shown below. When a
user tries to enter an invalid Email ID and submit the page, he will get the validation error
message, as shown below.

User Name Validation Example using Regular Expression:


Here is the requirement for validating the User Name property in our application:
 User Name can contain the first and last names with a single space.
 The last name is optional. If the last name is absent, there shouldn’t be any
space after the first name.
 Only upper and lower-case alphabets are allowed.
This requirement can be easily achieved in ASP.NET Core MVC using Regular Expression
Attributes. In the Employee.cs class file, decorate the UserName property with the
RegularExpression attribute, as shown below.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

{
87

[Required(ErrorMessage = "UserName is required")]

[RegularExpression(@"^(([A-za-z]+[\s]{1}[A-za-z]+)|([A-Za-z]+))$",

ErrorMessage = "UserName can contain the first and last names with a single space. " +

"The last name is optional. Only upper and lower case alphabets are allowed")]

public string UserName { get; set; }

[Required(ErrorMessage = "Email id is required")]

[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",

ErrorMessage = "Please Enter a Valid Email")]

public string EmailId { get; set; }

}
Notice that we are passing a regular expression string to the Attribute constructor. The
Regular Expression Attribute is great for pattern matching and ensures that the value for the
UserName property is in the format we want. Also, we use a verbatim literal (@ symbol)
string, as we don’t want escape sequences to be processed. Next, modify
the Create.cshtml file as follows.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="UserName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">
88

<input asp-for="UserName" class="form-control" placeholder="Please Enter User Name">

<span asp-validation-for="UserName" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="EmailId" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="EmailId" class="form-control" placeholder="Enter Your Email Id">

<span asp-validation-for="EmailId" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
Run the application and see the UserName field is working as expected.
89

Understanding and writing regular expressions is beyond the scope of this article. If you are
interested in learning to write regular expressions, here is a link from MSDN
http://msdn.microsoft.com/en-us/library/az24scfc.aspx
Range Data Annotation Attribute in ASP.NET Core MVC:
Suppose we have the Age field in the Employee model. If we don’t provide any validations,
we can enter any value, such as 5000 as the age and click on the Submit button, and then
the data will also be saved.
As we know, an employee who is 5000 years old is not possible. So, let’s validate the Age
field and force users to enter a value between 18 and 60. We can achieve this easily using
the RangeAttribute in an ASP.NET Core MVC Application. The Range attributes specify a
numerical number’s minimum and maximum constraints, as shown below.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[Required(ErrorMessage ="Age is Required")]

[Range(18, 60, ErrorMessage = "Age must be between 18 and 60")]

public int Age { get; set; }

}
90

In the above example, we set the employee’s age to be between 18 and 60 years to pass
the validation. Here, the first parameter of the attribute is the minimum value, the second
parameter is the maximum value, and the third parameter is the error message we want to
show when the validation fails. Next, modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="Age" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Age" class="form-control" placeholder="Please Enter Your Age">

<span asp-validation-for="Age" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
91

When a user tries to enter the age which is not between 18 and 60 and clicks on the submit
button, then he will get the validation error message as shown below:

At this point, we should not be able to enter any values outside the range of 18 and 60 for
the Age field.
Note: The Range attribute in ASP.NET Core MVC does not support the validation of the
DateTime fields.
MinLength and MaxLength Attribute in ASP.NET Core MVC:
These two attributes specify the Minimum and Maximum Length of a property. For example,
suppose we want to restrict the Employee Address as 5 as the minimum length and 25 as
the maximum length. In that case, we can decorate the address property with the
MinLength and Maxlength attributes, as shown below.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[MinLength(5, ErrorMessage = "The Address must be at least 5 characters")]

[MaxLength(25, ErrorMessage = "The Address cannot be more than 25 characters")]

public string Address { get; set; }

}
Next, modify the Create.cshtml file as follows.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";
92

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="Address" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Address" class="form-control" placeholder="Please Enter Your Address">

<span asp-validation-for="Address" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
Submitting the page by entering less than 5 characters will give the error as shown below.

DataType Attribute in ASP.NET Core MVC:


93

The DataType Attribute in the ASP.NET Core MVC Framework enables us to provide
runtime information about the specific purpose of the properties. For example, a property of
type string can have various scenarios, as it might hold an Email address, URL, Phone
Number, Credit Card, Password, etc. If you go to the definition of DataType Enum, you will
see the following named constants that we can specify using the DataType Attribute.

Let’s see an example of using the DataType attribute in the ASP.NET Core MVC
Application. Please modify the Employee model class as follows. Here, we are using the
DataType attribute to validate the Postal Code, Website URL, Password, Mobile Number,
Email Address, and Salary of an employee.
using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations.Schema;

namespace DataAnnotationsDemo.Models

public class Employee

{
94

[DataType(DataType.PostalCode, ErrorMessage = "Please Enter a Valid PIN/ZIP Code")]

public string PostalCode { get; set; }

[DataType(DataType.Url, ErrorMessage = "Please Enter a Valid URL")]

public string WebsiteURL { get; set; }

[DataType(DataType.Password, ErrorMessage = "Please Enter Password")]

public string Password { get; set; }

[DataType(DataType.PhoneNumber, ErrorMessage = "Please Enter a Valid Phone Number")]

public string Mobile { get; set; }

[DataType(DataType.EmailAddress, ErrorMessage = "Please Enter a valid Email Address")]

public string Email { get; set; }

[Range(10000, 1000000, ErrorMessage = "Please Enter a Value Between 10000 and


1000000")]

[DataType(DataType.Currency)]

[Column(TypeName = "decimal(18, 2)")]

public decimal Salary { get; set; }

}
Next, modify the Create.cshtml view as follows.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">
95

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="PostalCode" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="PostalCode" class="form-control" placeholder="Please Enter Your Postal


Code">

<span asp-validation-for="PostalCode" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="WebsiteURL" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="WebsiteURL" class="form-control" placeholder="Please Enter Your


Website URL">

<span asp-validation-for="WebsiteURL" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Password" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Password" class="form-control" placeholder="Please Enter Your


Password">

<span asp-validation-for="Password" class="text-danger"></span>

</div>

</div>
96

<div class="form-group row">

<label asp-for="Mobile" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Mobile" class="form-control" placeholder="Please Enter Your Mobile


Number">

<span asp-validation-for="Mobile" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Email" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Email" class="form-control" placeholder="Please Enter Your Email


Address">

<span asp-validation-for="Email" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Salary" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Salary" class="form-control" placeholder="Please Enter Your Salary">

<span asp-validation-for="Salary" class="text-danger"></span>

</div>

</div>

<div class="form-group row">


97

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
Compare Data Annotation Attribute in ASP.NET MVC Application:
The Compare Data Annotation Attribute in ASP.NET Core MVC Framework compares 2
model properties with the same value. The Compare attribute is commonly used to compare
credit card numbers and passwords. Let’s understand using the Compare attribute with an
example. For example, to ensure that the user has typed the correct password, we must
use the Password and ConfirmPassword of the employee model, as shown below.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[DataType(DataType.Password)]

[Required(ErrorMessage = "Password is Required")]

public string Password { get; set; }

[DataType(DataType.Password)]

[Required(ErrorMessage = "Confirm Password is Required")]

[Compare("Password", ErrorMessage = "Password and Confirm Password do not match")]

public string ConfirmPassword { get; set; }

}
Next, modify the Create.cshtml view as follows.
98

@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="Password" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Password" class="form-control" placeholder="Please Enter Your


Password">

<span asp-validation-for="Password" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="ConfirmPassword" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="ConfirmPassword" class="form-control" placeholder="Please Confirm


Your Password">

<span asp-validation-for="ConfirmPassword" class="text-danger"></span>

</div>

</div>

<div class="form-group row">


99

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
Now, if both Password and Confirm Password are not the same, the user will get the model
validation error, as shown below:

Real-Time Example:
Let us see one real-time example of understanding the data annotation attributes in an
ASP.NET core MVC application. We will be creating an Employee form to create a new
employee with proper validation both on the client and server sides. So, first, create a class
file named Gender.cs and then copy and paste the following code. This is going to be an
enum containing the gender-named constants.
namespace DataAnnotationsDemo.Models

public enum Gender

Male,

Female

}
100

}
Employee Model:
Next, modify the Employee.cs class file as follows.
using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations.Schema;

namespace DataAnnotationsDemo.Models

public class Employee

[Required(ErrorMessage = "First Name is Required")]

[StringLength(30, MinimumLength = 5, ErrorMessage = "First name should be between 5


and 30 characters")]

public string FirstName { get; set; }

[Required(ErrorMessage = "Last Name is Required")]

[StringLength(30, ErrorMessage = "Maximum 30 Characters allowed")]

public string LastName { get; set; }

[Required(ErrorMessage = "UserName is required")]

[RegularExpression(@"^(([A-za-z]+[\s]{1}[A-za-z]+)|([A-Za-z]+))$",

ErrorMessage = "UserName can contain the first and last names with a single space. " +

"The last name is optional. Only upper and lower case alphabets are allowed")]

public string UserName { get; set; }

[Required(ErrorMessage = "Please Select the Department")]

public string Department { get; set; }

[Required(ErrorMessage = "Email id is required")]

[DataType(DataType.EmailAddress, ErrorMessage = "Please Enter a valid Email Address")]


101

public string EmailId { get; set; }

[Required(ErrorMessage = "Age is Required")]

[Range(18, 60, ErrorMessage = "Age must be between 18 and 60")]

public int Age { get; set; }

[Required(ErrorMessage = "Please Select the Gender")]

public Gender Gender { get; set; }

[MinLength(5, ErrorMessage = "The Address must be at least 5 characters")]

[MaxLength(50, ErrorMessage = "The Address cannot be more than 50 characters")]

public string Address { get; set; }

[DataType(DataType.Url, ErrorMessage = "Please Enter a Valid URL")]

public string? WebsiteURL { get; set; }

[Range(10000, 1000000, ErrorMessage = "Please Enter a Value Between 10000 and


1000000")]

[DataType(DataType.Currency)]

[Column(TypeName = "decimal(18, 2)")]

public decimal Salary { get; set; }

//True means permanent and false means contractual

public bool IsPermanent { get; set; }

[Required(ErrorMessage = "Date of Joining is required")]

[DataType(DataType.Date, ErrorMessage = "Invalid Date Format")]

public DateTime DateOfJoining { get; set; }

public List<string> SkillSets { get; set; } = new List<string>();

[DataType(DataType.Password)]

[Required(ErrorMessage = "Password is Required")]


102

public string Password { get; set; }

[DataType(DataType.Password)]

[Required(ErrorMessage = "Confirm Password is Required")]

[Compare("Password", ErrorMessage = "Password and Confirm Password do not match")]

public string ConfirmPassword { get; set; }

}
Employee Controller:
Next, modify the Employee Controller as follows:
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Rendering;

namespace DataAnnotationsDemo.Controllers

public class EmployeeController : Controller

public IActionResult Create()

PrepareEmployeeViewModel();

return View();

[HttpPost]

public IActionResult Create(Employee employee)

if (ModelState.IsValid)
103

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");

PrepareEmployeeViewModel();

return View(employee);

public string Successful()

return "Employee Added Successfully";

private void PrepareEmployeeViewModel()

ViewBag.AllGenders = Enum.GetValues(typeof(Gender)).Cast<Gender>().ToList();

ViewBag.Departments = GetDepartments();

ViewBag.SkillSets = new List<string> { "Dot Net", "Java", "Python", "PHP", "Database" };

private List<SelectListItem> GetDepartments()

//You can get the data from the database and populate the SelectListItem

return new List<SelectListItem>()

new SelectListItem { Text = "IT", Value = "1" },


104

new SelectListItem { Text = "HR", Value = "2" },

new SelectListItem { Text = "Payroll", Value = "3" },

new SelectListItem { Text = "Admin", Value = "4" }

};

}
Employee Create View:
Next, modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="FirstName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="FirstName" class="form-control" placeholder="Please Enter Your


FirstName">

<span asp-validation-for="FirstName" class="text-danger"></span>

</div>

</div>

<div class="form-group row">


105

<label asp-for="LastName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="LastName" class="form-control" placeholder="Please Enter Your


LastName">

<span asp-validation-for="LastName" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="UserName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="UserName" class="form-control" placeholder="Please Enter Your


UserName/LoginName">

<span asp-validation-for="UserName" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Department" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<select asp-for="Department" class="form-select"

asp-items="ViewBag.Departments">

<option value="">Please Select</option>

</select>

<span asp-validation-for="Department" class="text-danger"></span>

</div>
106

</div>

<div class="form-group row">

<label asp-for="EmailId" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="EmailId" class="form-control" placeholder="Please Enter Your EmailId">

<span asp-validation-for="EmailId" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Age" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Age" class="form-control" type="number">

<span asp-validation-for="Age" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="IsPermanent" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="IsPermanent" class="custom-control-input">

</div>

</div>

<div class="form-group row">

<label asp-for="Gender" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">
107

@foreach (var gender in ViewBag.AllGenders)

<label class="radio-inline">

<input type="radio" asp-for="Gender" value="@gender" id="Gender@(gender)"


/>@gender<br />

</label>

<span asp-validation-for="Gender" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="Address" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Address" class="form-control" placeholder="Please Enter Your Address">

<span asp-validation-for="Address" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="WebsiteURL" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="WebsiteURL" type="url" class="form-control" placeholder="Please Endter


Your Website URL">

<span asp-validation-for="WebsiteURL" class="text-danger"></span>

</div>
108

</div>

<div class="form-group row">

<label asp-for="Salary" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Salary" type="number" class="form-control" placeholder="Please Confirm


Your Salary">

<span asp-validation-for="Salary" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="DateOfJoining" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="DateOfJoining" class="form-control" placeholder="Please Enter Your


UserName/LoginName">

<span asp-validation-for="DateOfJoining" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="SkillSets" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<style>

.form-check {

display: inline-block;

margin-right: 20px;
109

</style>

<div class="hobby-list" style="display: flex; flex-wrap: wrap;">

@{

int counter = 0;

foreach (var skill in ViewBag.SkillSets as List<string>)

<div class="form-check">

<label class="form-check-label" for="@("hobby" + counter)">@skill</label>

<input class="form-check-input" type="checkbox" name="SkillSets" value="@skill"


id="@("skill" + ++counter)" />

</div>

</div>

</div>

</div>

<div class="form-group row">

<label asp-for="Password" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Password" class="form-control" placeholder="Please Enter Your


Password">

<span asp-validation-for="Password" class="text-danger"></span>

</div>
110

</div>

<div class="form-group row">

<label asp-for="ConfirmPassword" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="ConfirmPassword" class="form-control" placeholder="Please Confirm


Your Password">

<span asp-validation-for="ConfirmPassword" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

How to Create Custom Data Annotation Attribute in ASP.NET Core MVC


In this article, I will discuss How to Create Custom Data Annotation Attributes in
ASP.NET Core MVC Applications with Real-Time Examples. Please read our previous
article discussing Data Annotation Attributes in ASP.NET Core MVC.
111

What is Custom Data Annotation Attribute in ASP.NET Core MVC?


In ASP.NET Core MVC, a Custom Data Annotation Attribute creates a custom validation
rule that can be applied to model properties. If the Built-in Data Annotation Attribute does
not fulfill your validation requirements, then we need to create a Custom Validation Attribute
as per our business requirement.
Custom Data Annotation Attribute Example in ASP.NET Core MVC
Let us understand how to create a custom data annotation attribute for the “Date of Joining”
property, ensuring that the data provided meets certain criteria, such as the Joining Date is
not a future date. It should be a past date or today
Create the Custom Attribute Class
First, you need to create a class for the custom attribute. This class will inherit
from ValidationAttribute, which is part of
the System.ComponentModel.DataAnnotations namespace and override
the IsValid method to provide your custom validation logic. So, create a class file
named ValidJoiningDateAttribute.cs and copy and paste the following code. This class
overrides the IsValid method to check whether the date is in the future and returns a
ValidationResult accordingly.

Traveling across Quang Binh to stunning cinematic locations00:13 / 03:1710 Sec

using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class ValidJoiningDateAttribute : ValidationAttribute

public ValidJoiningDateAttribute()

ErrorMessage = "The joining date cannot be in the future.";

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

if (value is DateTime)

{
112

DateTime joiningDate = (DateTime)value;

if (joiningDate > DateTime.Now)

return new ValidationResult(ErrorMessage);

return ValidationResult.Success;

}
Code Explanation:
The ValidJoiningDateAttribute class extends the ValidationAttribute class provided by
the .NET framework. ValidationAttribute is a base class used to create custom validation
attributes that can be applied to model properties.
Constructor:
The constructor initializes the new attribute and sets the ErrorMessage property inherited
from ValidationAttribute. This message is displayed when the validation fails.
IsValid Method:
This method overrides the IsValid method of the base ValidationAttribute class. It is called
automatically when the framework performs model validation. The parameters are:
 value: This is the value of the property to which the attribute is applied.
 validationContext: This provides context about the validation operation,
such as the object being validated.
Validation Logic:
 The validation logic first checks if the value being validated is of type
DateTime. If it is, the code casts value to DateTime and assigns it to
joiningDate.
 Next, it checks if joiningDate is greater than DateTime.Now (i.e., it checks if
the date is in the future). If this condition is true, the method returns a new
ValidationResult containing the error message specified in the constructor.
This indicates that the validation has failed.
Successful Validation:
 If the value is not a DateTime or the joiningDate is not in the future, the
method returns ValidationResult.Success indicates that the validation passed
successfully.
Apply the Attribute to a Model Property
Now that you have created the custom validation attribute, you can apply it to properties in
your model classes. So, create a class file named Employee.cs and copy and paste the
113

following code. Here, you can see we have applied the Custom ValidJoiningDate Attribute
to the DateOfJoining property.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[Required(ErrorMessage = "Date of Joining is required")]

[DataType(DataType.Date, ErrorMessage = "Invalid Date Format")]

[ValidJoiningDate(ErrorMessage ="Date of Joining Cannot be Future Date")]

public DateTime DateOfJoining { get; set; }

}
Modify the Employee Controller:
Next, modify the Employee Controller as follows. Please ensure the model state is checked
for validity in your action methods before processing the data.
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class EmployeeController : Controller

public IActionResult Create()

return View();

}
114

[HttpPost]

public IActionResult Create(Employee employee)

if (ModelState.IsValid)

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");

return View(employee);

public string Successful()

return "Employee Added Successfully";

}
Create.cshtml View:
Next, add Create.cshtml view and copy and paste the following code. Ensure that you use
HTML helpers like Html.ValidationMessageFor() or Tag Helper like asp-validation-for to
display validation messages.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>
115

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="DateOfJoining" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="DateOfJoining" class="form-control" placeholder="Please Enter Your


UserName/LoginName">

<span asp-validation-for="DateOfJoining" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
Test Your Application
Finally, test your application to ensure the custom validation attribute works as expected.
Try submitting a form with a future date for the “Date of Joining” field and verify that the
error message is displayed, as shown in the image below.
116

By following these steps, we have successfully created and applied a custom data
annotation attribute in ASP.NET Core MVC, specifically for validating a “Date of Joining”
property. This not only ensures data integrity but also enhances your application’s
robustness. The above validation attribute only works on the server side, not on the client
side. Let us proceed and see how to enable client-side validation.
How Client-Side Validation Works in ASP.NET Core MVC?
Before understanding how to enable Client-Side Validation in ASP.NET Core MVC for
Custom Data Annotation Attributes, let’s first understand how client-side validation works in
ASP.NET Core MVC when using Data Annotation.
When we use Razor syntax to create form fields linked to model properties decorated with
data annotation attributes, the ASP.NET Core MVC Framework automatically generates
HTML elements with data attributes that describe the validation rules. The jQuery Validate
Unobtrusive library reads these attributes.
When we add a data annotation to a model property, the ASP.NET Core MVC tag helpers
will automatically generate the necessary data-val-* attributes to represent the validation
rules for that property. Let us take the following class. Here, we have applied the Required
Data Annotation attribute with the DateOfJoining model property.
public class Employee

[Required(ErrorMessage = "Date of Joining is required")]

public DateTime DateOfJoining { get; set; }

}
We can then use ASP.NET Core MVC tag helpers in our Razor view to generate the HTML
as follows:
<input asp-for="DateOfJoining" class="form-control">

<span asp-validation-for="DateOfJoining" class="text-danger"></span>


Once you run the application and view the Page source, then you will see the Razor Engine
will generate the following HTML code behind the scenes:
<input class="form-control" type="datetime-local" data-val="true" data-val-required="Date
of Joining is required" id="DateOfJoining" name="DateOfJoining" value="">
117

<span class="text-danger field-validation-valid" data-valmsg-for="DateOfJoining" data-


valmsg-replace="true"></span>
Understanding the data-* Attributes:
 data-val=”true”: This attribute signifies that the input element has validation
rules to be enforced. Its presence enables jQuery Unobtrusive Validation to
handle this input for validation purposes.
 data-val-required=”Date of Joining is required”: This attribute is used for
the ‘required’ validation rule. It indicates that the input field cannot be left
empty. The string “Date of Joining is required” is the error message displayed
when the validation fails (i.e., if the field is submitted without a value).
 data-valmsg-for=”DateOfJoining”: This attribute links the span element to
the DateOfJoining input field as its validation message container. When the
input field’s validation state changes, this span is updated with the
appropriate validation message.
 data-valmsg-replace=”true”: This attribute tells jQuery Validation
Unobtrusive to replace the entire content of the span with the validation error
message if there is one. If set to false, the error message would be appended
to the content of the span, preserving any existing content.
The jQuery Validate Unobtrusive library parses these data attributes and sets up the jQuery
Validate plugin accordingly. This plugin then performs client-side validation based on these
rules. If an input fails validation, the form will display an error message and prevent the form
from being submitted.
Enabling Client-Side Validation with Custom Data Annotation Attribute:
Enabling client-side validation for a custom data annotation attribute in ASP.NET Core MVC
involves several more steps than server-side validation. Let us see how to modify the
ValidJoiningDateAttribute to include client-side validation:
Modify the Custom Attribute Class
First, we need to ensure our Custom Data Annotation Attribute can output the appropriate
HTML5 data attributes that jQuery validation can use. To do this, our Custom Data
Annotation Attribute will implement the IClientModelValidator interface. So, modify
the ValidJoiningDateAttribute class as follows.
The following ValidJoiningDateAttribute class defines an enhanced custom validation
attribute in ASP.NET Core MVC. This attribute not only checks server-side validation
constraints but also enables client-side validation. It uses the ValidationAttribute for
server-side validation and the IClientModelValidator interface to add client-side
validation.
using System.ComponentModel.DataAnnotations;

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace DataAnnotationsDemo.Models

public class ValidJoiningDateAttribute : ValidationAttribute, IClientModelValidator


118

public ValidJoiningDateAttribute()

ErrorMessage = "The joining date cannot be in the future.";

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

if (value is DateTime)

DateTime joiningDate = (DateTime)value;

if (joiningDate > DateTime.Now)

return new ValidationResult(ErrorMessage);

return ValidationResult.Success;

public void AddValidation(ClientModelValidationContext context)

if (context == null)

throw new ArgumentNullException(nameof(context));

}
119

MergeAttribute(context.Attributes, "data-val", "true");

MergeAttribute(context.Attributes, "data-val-ValidJoiningDate", ErrorMessage);

private bool MergeAttribute(IDictionary<string, string> attributes, string key, string


value)

if (!attributes.ContainsKey(key))

attributes.Add(key, value);

return true;

return false;

}
Code Explanation:
IsValid Method: Enable Server-Side Validation. We have already explained this method in
our previous example.
AddValidation Method:
 Parameter Check: Initially, the method checks if the context is null, throwing
an exception if it is. This check ensures the method has the necessary
context to operate.
 Adding Validation Attributes: The method uses the MergeAttribute to add
HTML5 data attributes to the form input element that will be used for client-
side validation:
 data-val=”true” signals to the jQuery validation framework that the field has
validation rules.
 data-val-ValidJoiningDate=”ErrorMessage” adds a custom attribute with
the validation error message. The key data-val-ValidJoiningDate is a unique
identifier for this particular validation rule.
MergeAttribute Method:
This helper method checks if an attribute already exists in the dictionary. If it doesn’t, the
attribute is added with the specified key and value. The method returns true if the attribute
120

was added and false if it was already present. This functionality prevents overwriting of any
existing attributes that may have been set elsewhere.
Client-Side Validation Logic
The client-side part of the validation logic is not explicitly detailed in this code but generally
involves:
 JavaScript Integration: You would need to write custom JavaScript or
jQuery that uses the data attributes (data-val-ValidJoiningDate) to perform
validation on the client side.
 Validation Framework: ASP.NET Core typically uses jQuery Unobtrusive
Validation for client-side validation. Your JavaScript would integrate with this
framework, ensuring that the client validates that the joining date is not in the
future before the form data is submitted to the server.
Add Client-Side JavaScript
Using jQuery and the jQuery Validation library, we can implement the client-side validation
logic. Create a JavaScript file that will extend jQuery validation to handle your custom
attribute. Add a new JavaScript file named JoiningDateValidate.js into the wwwroot folder
as shown below:

Then, copy and paste the following code into the JoiningDateValidate.js file. The following
code is self-explained, so please go through the comment lines for a better understanding.
// Define a custom method for jQuery validation to check the "Date of Joining".

jQuery.validator.addMethod('ValidJoiningDate', function (value, element) {

// Directly return true for empty values, which allows the field to be optional.

if (!value) {

return true;

}
121

// Compare input date with the current date, ignoring time of day.

var inputDate = new Date(value);

var today = new Date();

today.setHours(0, 0, 0, 0); // Normalize today's date by removing time components.

// Validate that the input date is not in the future.

return inputDate <= today;

}, 'The joining date cannot be in the future.'); // Default error message.

// Integrate the custom method with jQuery unobtrusive validation.

jQuery.validator.unobtrusive.adapters.addBool('ValidJoiningDate');
Include the JavaScript File in Your View
Make sure to reference the JavaScript file you created in the views that use the validation.
So, modify the Create.cshtml file as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<script>

console.log('jQuery version:', $.fn.jquery);

</script>

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="DateOfJoining" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">
122

<input asp-for="DateOfJoining" class="form-control">

<span asp-validation-for="DateOfJoining" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>

<script src="~/js/joiningdatevalidate.js"></script>
Applying Custom Data Annotation Attribute:
Please make sure to apply the Custom Data Annotation Attribute with DateOfJoining
Property of Employee Model as follows:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[Required(ErrorMessage = "Date of Joining is required")]


123

[DataType(DataType.Date, ErrorMessage = "Invalid Date Format")]

[ValidJoiningDate(ErrorMessage ="Date of Joining Cannot be Future Date")]

public DateTime DateOfJoining { get; set; }

}
Test the Validation
Now that both client-side and server-side validations are in place, test the application, and it
should work as expected:
 Client-side: Try entering a future date in your form and see if the form
prevents submission without needing to go to the server.
 Server-side: Disable JavaScript in your browser and try the same to ensure
the server-side validation is still working as expected.
Now, if you view the page source code, then you will see the following HTML code:
<input class="form-control" type="date" data-val="true" data-val-required="Date of Joining
is required" data-val-ValidJoiningDate="Date of Joining Cannot be Future Date"
id="DateOfJoining" name="DateOfJoining" value="">

<span class="text-danger field-validation-valid" data-valmsg-for="DateOfJoining" data-


valmsg-replace="true"></span>
Here,
 type=”date”: This specifies that the input field is meant to enter a date.
Browsers that support HTML5 will render a date picker to facilitate the input of
date values.
 data-val=”true”: This attribute signals validation rules are applied to this
field. JQuery Validate Unobtrusive or similar libraries use it to initialize
validation on this element.
 data-val-required=”Date of Joining is required”: This is a validation
attribute indicating that this field must be filled out. If left empty, the specified
error message “Date of Joining is required” will be displayed.
 data-val-ValidJoiningDate=”Date of Joining Cannot be Future
Date”: This is a custom validation attribute, presumably defined in the server-
side code (ASP.NET Core MVC). It checks that the entered date is not in the
future. If the validation fails, it displays the message “Date of Joining Cannot
be Future Date.”
 data-valmsg-for=”DateOfJoining”: This attribute links the <span> to the
<input> with id=”DateOfJoining”. It tells the validation framework to display
any error messages related to the “DateOfJoining” input field in this <span>.
 data-valmsg-replace=”true”: This attribute tells the validation framework to
replace the content of this <span> with the validation message when an error
occurs. If false, the error message will be appended to any existing content
inside the <span>.
124

Why Custom Data Annotation Attribute in ASP.NET Core MVC?


Custom data annotation attributes in ASP.NET Core MVC are used for several reasons,
primarily to enhance the functionality of model validation, provide meaningful error
messages, and ensure that data is valid as per the business rules before it reaches the
underlying database. The following are the reasons why we might use custom data
annotation attributes in ASP.NET Core Applications:
 Custom Validation Rules: While ASP.NET Core MVC provides a set of
standard validation attributes (like Required, Range, StringLength, etc.),
these may not cover all scenarios specific to your business logic. Custom
attributes allow us to define these rules explicitly.
 Reusable Validation Logic: Once defined, custom attributes can be reused
across different models within your application. This avoids duplication of
validation code and ensures consistency in how data validation is applied
throughout your application.
 Client and Server Validation: Custom data annotations can support server-
side and client-side validation. This means you can prevent the form from
being submitted until the input meets all specified criteria, enhancing the user
experience by providing immediate feedback.

Custom Data Annotation Attribute Real-Time Examples in ASP.NET Core MVC


In this article, I will discuss Custom Data Annotation Attribute Real-Time Examples in
ASP.NET Core MVC. Please read our previous article discussing How to Create Custom
Data Annotation Attributes in ASP.NET Core MVC Applications. Let’s understand a few
real-world scenarios where custom data annotations might come in handy:
Date of Birth Validation:
Suppose you’re building a website where users need to be at least 18 years old to register.
You can create a custom validation attribute to ensure this. To create a custom data
annotation attribute in ASP.NET Core MVC that validates whether the Date of Birth (DOB)
indicates an age greater than 18 years, you’ll need to follow a few steps. These include
creating a custom validation attribute, enabling client-side validation, and applying it to your
model.
Create the Custom Validation Attribute
You need to create a custom attribute class that inherits from the ValidationAttribute and
IClientModelValidator interface and overrides the IsValid method, which is used for Server
Side Validation. Also, the AddValidation method of the IClientModelValidator interface,
which is required for Client-Side Validation, must be implemented. So, create a class file
named MinimumAgeAttribute.cs and copy and paste the following code.
using System.ComponentModel.DataAnnotations;

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace DataAnnotationsDemo.Models

{
125

public class MinimumAgeAttribute : ValidationAttribute, IClientModelValidator

private readonly int _minimumAge;

public MinimumAgeAttribute(int minimumAge)

_minimumAge = minimumAge;

ErrorMessage = $"You must be at least {minimumAge} years old.";

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

if (value == null)

return new ValidationResult("Date of Birth is required.");

if (value is DateTime)

DateTime dateOfBirth = (DateTime)value;

if (dateOfBirth > DateTime.Now.AddYears(-_minimumAge))

return new ValidationResult(ErrorMessage);

else
126

return new ValidationResult("Invalid date format.");

return ValidationResult.Success;

public void AddValidation(ClientModelValidationContext context)

if (context == null)

throw new ArgumentNullException(nameof(context));

MergeAttribute(context.Attributes, "data-val", "true");

MergeAttribute(context.Attributes, "data-val-minimumage", ErrorMessage);

MergeAttribute(context.Attributes, "data-val-minimumage-minage",
_minimumAge.ToString());

private bool MergeAttribute(IDictionary<string, string> attributes, string key, string


value)

if (!attributes.ContainsKey(key))

attributes.Add(key, value);

return true;

return false;

}
127

}
Create a JavaScript File for Client-Side Validation
Next, we need to write JavaScript that works with the jQuery validation library to add a
method for handling your custom MinimumAge validation. So, create a JavaScript file
named minimum-age-validation.js within the wwwroot/js folder and copy and paste the
following code.

Traveling across Quang Binh to stunning cinematic locations00:19 / 03:1710 Sec

// minimum-age-validation.js

jQuery.validator.addMethod("minimumage", function (value, element, params) {

if (!value) {

return true; // Not validating emptiness here, required attribute should handle this

var dateOfBirth = new Date(value);

var minimumAgeDate = new Date();

minimumAgeDate.setFullYear(minimumAgeDate.getFullYear() - parseInt(params));

return dateOfBirth <= minimumAgeDate;

}, function (params, element) {

return $(element).data('val-minimumage');

});

jQuery.validator.unobtrusive.adapters.add("minimumage", ["minage"], function (options) {

options.rules["minimumage"] = options.params.minage;

options.messages["minimumage"] = options.message;

});
Apply the Attribute to Your Model
Next, apply this attribute to the DOB property in your model. So, let us create a class file
named Employee.cs and then copy and paste the following code:
128

using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[Required(ErrorMessage = "Date of Birth is required")]

[DataType(DataType.Date, ErrorMessage = "Invalid Date Format")]

[MinimumAge(18, ErrorMessage = "You must be at least 18 years old.")]

public DateTime DateOfBirth { get; set; }

}
Use the Model in Your MVC Controller
Ensure your MVC controller is set up to use model validation. So, modify the Employee
Controller as follows:
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class EmployeeController : Controller

public IActionResult Create()

return View();

[HttpPost]
129

public IActionResult Create(Employee employee)

if (ModelState.IsValid)

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");

return View(employee);

public string Successful()

return "Employee Added Successfully";

}
Update the View to Display Validation Messages
In your view, ensure you display validation messages related to the DOB field. So, modify
the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">
130

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="DateOfBirth" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="DateOfBirth" class="form-control">

<span asp-validation-for="DateOfBirth" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>

<script src="~/js/minimum-age-validation.js"></script>
Testing:
Now, run the application and try to enter an Age that is less than 18 years, and then you will
get the required error message as shown in the image below:
131

Password Strength Checker:


Nowadays, most applications need users to set strong passwords. We can achieve this very
easily using Custom Data Annotation Attribute in ASP.NET Core MVC Application. We are
going to enforce the following rules on the password:
Password should contain uppercase, lowercase, numbers, and special characters (@, #, $,
&), minimum 8 Characters
Create the Custom Validation Attribute
First, we will define the custom attribute by inheriting from the ValidationAttribute class for
server-side validation and implementing the IClientModelValidator interface for client-side
validation. So, create a class file named PasswordStrengthAttribute.cs and then copy
and paste the following code:
using System.ComponentModel.DataAnnotations;

using System.Text.RegularExpressions;

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace DataAnnotationsDemo.Models

public class PasswordStrengthAttribute : ValidationAttribute, IClientModelValidator

public PasswordStrengthAttribute()

ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter,
one number, one special character (@,#,$,&) and be at least 8 characters long.";

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)
132

if (value == null || string.IsNullOrEmpty(value.ToString()))

return new ValidationResult("Password is required.");

var password = value.ToString();

var regex = new Regex(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$&])[A-Za-z\d@#$&]


{8,}$");

if (!regex.IsMatch(password))

return new ValidationResult(ErrorMessage);

return ValidationResult.Success;

public void AddValidation(ClientModelValidationContext context)

if (context == null)

throw new ArgumentNullException(nameof(context));

context.Attributes.Add("data-val", "true");

context.Attributes.Add("data-val-passwordstrength", ErrorMessage);

}
Explanation
Regular Expression: The Regex pattern checks for:
133

 (?=.*[a-z]): At least one lowercase letter.


 (?=.*[A-Z]): At least one uppercase letter.
 (?=.*\d): At least one digit.
 (?=.*[@#$&]): At least one special character from the set @, #, $, &.
 [A-Za-z\d@#$&]{8,}: The password must be at least 8 characters long and
contain only the characters specified (no other special characters are
allowed).
Server-side Validation: The IsValid method implements the server-side validation logic,
using the regex pattern to validate the password string.
Client-side Validation: The AddValidation method adds the necessary data attributes to
enable client-side validation using ASP.NET Core’s unobtrusive validation.
Add Client-Side Validation Script
Next, you need to write custom JavaScript that works with the jQuery validation library to
add a method for handling your custom Password validation. So, create a JavaScript file
named Password-Strength-Validation.js within the wwwroot/js folder and copy and paste
the following code.
jQuery.validator.addMethod('passwordstrength', function (value, element) {

if (!value) {

return true; // Don't validate empty values

return this.optional(element) || /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$&])[A-Za-z\


d@#$&]{8,}$/.test(value);

}, 'Password must contain at least one uppercase letter, one lowercase letter, one number, one
special character (@,#,$,&) and be at least 8 characters long.');

jQuery.validator.unobtrusive.adapters.add('passwordstrength', function (options) {

options.rules['passwordstrength'] = {};

options.messages['passwordstrength'] = options.message;

});
Usage in a Model
Apply the custom validation attribute to a property in your model. Please modify the
Employee model as follows:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

{
134

public class Employee

[Required(ErrorMessage = "Date of Birth is required")]

[DataType(DataType.Password)]

[PasswordStrength]

public string Password { get; set; }

}
Update the View to Display Validation Messages
In your view, ensure you display validation messages related to the Password field. So,
modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">

<div class="form-group row">

<label asp-for="Password" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Password" class="form-control">

<span asp-validation-for="Password" class="text-danger"></span>

</div>

</div>
135

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>

<script src="~/js/minimum-age-validation.js"></script>
Testing:
Now, run the application and try to enter an invalid password and then you should get the
following error message as shown in the below image:

Date Range Checker:


Creating a custom data annotation attribute in ASP.NET Core MVC to validate a date range
involving both client-side and server-side validation can be very useful. Let us create a
Custom Data Annotation Attribute with the following Rules:
 From and ToDate should not be greater than Current Date
 ToDate should not be greater than From Date
 ToDate is mandatory if the User Selects the From Date
Create the Custom Validation Attribute
We will start by creating a custom attribute that can be used on a view model. This attribute
will handle the server-side validation. First, we will define the custom attribute by inheriting
136

from the ValidationAttribute class for server-side validation and implementing the
IClientModelValidator interface for client-side validation. So, create a class file
named DateRangeValidationAttribute.cs and then copy and paste the following code:
using System.ComponentModel.DataAnnotations;

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace DataAnnotationsDemo.Models

public class DateRangeValidationAttribute : ValidationAttribute, IClientModelValidator

public string OtherPropertyName { get; set; }

public DateRangeValidationAttribute(string otherPropertyName)

OtherPropertyName = otherPropertyName;

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

var otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherPropertyName);

if (otherPropertyInfo == null)

return new ValidationResult($"Unknown property: {OtherPropertyName}");

var otherDate = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null) as


DateTime?;

var thisDate = value as DateTime?;

if (thisDate.HasValue && thisDate > DateTime.Today)


137

return new ValidationResult("Date cannot be in the future.");

if (otherDate.HasValue && thisDate.HasValue)

if (OtherPropertyName == "FromDate" && thisDate < otherDate)

return new ValidationResult("To date cannot be earlier than from date.");

else if (OtherPropertyName == "ToDate" && thisDate > otherDate)

return new ValidationResult("From date cannot be later than to date.");

return ValidationResult.Success;

public void AddValidation(ClientModelValidationContext context)

MergeAttribute(context.Attributes, "data-val", "true");

MergeAttribute(context.Attributes, "data-val-daterange", ErrorMessage ?? "Date is out of the


allowed range.");

MergeAttribute(context.Attributes, $"data-val-daterange-other", OtherPropertyName);

}
138

private void MergeAttribute(IDictionary<string, string> attributes, string key, string


value)

if (!attributes.ContainsKey(key))

attributes.Add(key, value);

}
Usage in a Model
Next, apply this attribute to your model. This example assumes that your model has
properties FromDate and ToDate. Let us modify the Employee.cs class file as follows. We
want to find the employees who joined the organization between these dates.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

[DateRangeValidation("ToDate", ErrorMessage = "From date cannot be in the future and


must be before To date.")]

public DateTime? FromDate { get; set; }

[DateRangeValidation("FromDate", ErrorMessage = "To date is required if From date is


selected and cannot be in the future or before From date.")]

public DateTime? ToDate { get; set; }

}
139

}
Add Client-Side Validation Script
Next, you need to write custom JavaScript that works with the jQuery validation library to
add a method for handling your custom Date Rangle validation. So, create a JavaScript file
named Date-Range-Validation.js within the wwwroot/js folder and copy and paste the
following code.
$.validator.addMethod('daterange', function (value, element, params) {

var otherPropName = $(element).data('valDaterangeOther');

var otherValue = $('#' + otherPropName).val();

if (otherPropName && otherValue) {

var thisDate = new Date(value);

var otherDate = new Date(otherValue);

if (otherPropName === 'FromDate') {

return thisDate >= otherDate && thisDate <= Date.now();

} else {

return thisDate <= otherDate && thisDate <= Date.now();

return true;

}, function (params, element) {

return $(element).data('valDaterange');

});

$.validator.unobtrusive.adapters.add('daterange', [], function (options) {

options.rules['daterange'] = true;

options.messages['daterange'] = options.message;

});
140

Update the View to Display Validation Messages


In your view, ensure you display validation messages related to the Password field. So,
modify the Create.cshtml view as follows:
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Create";

<h1>Create Employee</h1>

<div class="row">

<form asp-controller="Employee" asp-action="Create" method="post"


enctype="multipart/form-data" class="mt-3">

<div class="form-group row">

<label asp-for="FromDate" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="FromDate" class="form-control">

<span asp-validation-for="FromDate" class="text-danger"></span>

</div>

</div>

<div class="form-group row">

<label asp-for="ToDate" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="ToDate" class="form-control">

<span asp-validation-for="ToDate" class="text-danger"></span>

</div>

</div>
141

<div class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>

<script src="~/js/date-range-validation.js"></script>
Testing:
Now, run the application and try to enter an invalid date range, and then you should get the
error message as shown in the below image:

Remote Validation in ASP.NET Core MVC


In this article, I will discuss How to Implement Remote Validations using Data
Annotation in ASP.NET Core MVC Applications with Real-Time Examples. Please read
our previous article discussing Custom Data Annotation Attribute Real-Time Examples
in ASP.NET Core MVC Application. At the end of this article, you will understand what
Remote Validation is, why we need it, and how to implement Remote Validation in
ASP.NET Core MVC applications.
What is Remote Validation in ASP.NET Core MVC Application?
142

Remote Validation in ASP.NET Core MVC is a technique for performing client-side


validation of input fields asynchronously by calling server-side code. This helps ensure that
the data entered by users meets specific criteria without needing a full-page refresh,
providing a good user experience. It can be useful for checking if a username or email
address is already in use, verifying complex patterns, or ensuring data consistency.
For example, sometimes, to check whether a field value is valid, we may need to make a
database call. A classic example of this is the Gmail user registration page. To register a
user, we need a unique email. So, to check if the email is not taken already, we have to call
the server and check the database table. Remote Validation is useful in situations like this.
Remote Validation Real-Time Example in ASP.NET Core MVC:
Let us understand Remote Validation in ASP.NET Core MVC with one Real-Time Example.
We will create one Registration Page where we will ask the User to Enter his Email, User
Name, and Password. If the entered UserName and Email address are already taken by
someone else, then we will show the following error message immediately without
submitting a full page. Also, we are providing one or more suggestions for use instead.
Traveling across Quang Binh to stunning cinematic locations

So, we need to do the following For Email and User Name fields:
 Email: It must be Unique in the Database and should be a Proper Valid Email
Address. If the email address has already been taken by someone else, it
should give alternative suggestions for the email using the proper email
format. It is very similar to the Gmail Account Registration.
 User Name: It Must be Unique in the Database, and the User Name should
Contain both Lower-Case and Upper-Case Alphabets, digits from 0 to 9, with
some special characters such as @, #, and $, and it should be at least 8
characters. Also, if the username has already been taken by someone else,
then we need to give some alternative suggestions.
To implement the above validation for the email and user name in an ASP.NET Core MVC
Application, we need a combination of client-side and server-side validations. Let’s proceed
and see how we can implement this step by step.
Creating a New ASP.NET Core Project:
143

First, create a new ASP.NET Core Project using the Model View Controller Template and
give the project name RemoteValidationDemo. Let’s create the project using the latest .NET
Core Version so that we can also use the latest EF Core Packages.
Once the Project is created, as we are going to work with SQL Server Database using
Entity Framework Core Code First Approach, so, we need to install the following two NuGet
Packages:
 Microsoft.EntityFrameworkCore.Tools
 Microsoft.EntityFrameworkCore.SqlServer
You can install the above two packages using NuGet Package Manager for the solution or
using Package Manager Console. As we are working with the latest version of .NET Core,
open Package Manager Console and then execute the following command.
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Creating Model:
Please add the following User.cs class file to the Models folder. EF Core will use this
Model to create the database table.
namespace RemoteValidationDemo.Models

public class User

public int Id { get; set; }

public string Email { get; set; }

public string UserName { get; set; }

public string Password { get; set; }

}
Configure the Database Connection
Next, we are going to store the connection string in the appsettings.json file. So, add your
database connection string in the appsettings.json file as follows:
{

"Logging": {

"LogLevel": {

"Default": "Information",
144

"Microsoft.AspNetCore": "Warning"

},

"AllowedHosts": "*",

"ConnectionStrings": {

"EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=EFCoreMVCDB;Trusted_Connection=True;TrustServerC
ertificate=True;"

}
Note: It will use the EFCoreMVCDB data if it exists in the SQL Server; otherwise, it will
create and use the EFCoreMVCDB.
Configure DbContext
Create a DbContext class for the application. Add a class
named EFCoreDBContext.cs within the Models folder, and then copy and paste the
following code.
using Microsoft.EntityFrameworkCore;

namespace RemoteValidationDemo.Models

public class EFCoreDBContext : DbContext

public EFCoreDBContext(DbContextOptions<EFCoreDBContext> options)

: base(options)

public DbSet<User> Users { get; set; }

}
145

}
Registering the Connection String and DbContext Class:
Next, we need to configure the connection string and register the context class to use the
Connection string in the Program class. So, please add the following to the Program class:
//Configure the ConnectionString and DbContext class

builder.Services.AddDbContext<EFCoreDBContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"))
;

});
Create Database Migration
Next, we need to generate and execute the Migration so that it will create and update the
database schema. So, open the Package Manager Console and Execute the add-migration
and update-database commands as follows. You can give your migration any name. Here, I
am giving it EFCoreDBMig1. The name that you are giving it should not be given earlier.

With this, our Database with Users database table should be created as shown in the image
below:
146

Before proceeding with the Remote Validations, let us insert some data into the Users
database table by executing the following INSERT SQL statements, which we will use while
performing the Remote Validation checks.
INSERT INTO Users (Email, UserName, Password) VALUES

('pranaya@example.com', 'Pranaya@121', 'Pass@1234'),

('anurag@example.com', 'Anurag#@2005', 'Pass@4567'),

('hina@example.com', 'hina@121', 'Pass@5678'),

('ramesh@example.com', 'Ramesh@1215', 'Pass@6789');


Create a Service for Generating Unique Email and User Name
Suggestions:
So, create a class file named GenerateSuggestions.cs within the Models folder and copy
and paste the following code. In the following example code,
the GenerateUniqueEmailsAsync and GenerateUniqueUsernamesAsync methods are
used to generate unique suggestions for user emails and usernames. This functionality is
especially useful in systems where users must have unique identifiers and the proposed
email or username has already been taken by another user.
// Required for asynchronous LINQ methods like AnyAsync

using Microsoft.EntityFrameworkCore;

namespace RemoteValidationDemo.Models
147

public class GenerateSuggestions

private readonly EFCoreDBContext _context;

public GenerateSuggestions(EFCoreDBContext context)

_context = context;

public async Task<string> GenerateUniqueEmailsAsync(string baseEmail, int count = 1)

var suggestions = new List<string>();

string emailPrefix = baseEmail.Split('@')[0];

string emailDomain = baseEmail.Split('@')[1];

string suggestion;

while (suggestions.Count < count)

do

suggestion = $"{emailPrefix}{new Random().Next(100, 999)}@{emailDomain}";

//Use AnyAsync to asynchronously check if the email exists

} while (await _context.Users.AnyAsync(u => u.Email == suggestion) ||


suggestions.Contains(suggestion));

suggestions.Add(suggestion);

}
148

return string.Join(", ", suggestions);

public async Task<string> GenerateUniqueUsernamesAsync(string baseUsername, int


count = 1)

var suggestions = new List<string>();

string suggestion;

while (suggestions.Count < count)

do

suggestion = $"{baseUsername}{new Random().Next(100, 999)}";

} while (await _context.Users.AnyAsync(u => u.UserName == suggestion) ||


suggestions.Contains(suggestion));

suggestions.Add(suggestion);

return string.Join(", ", suggestions);

}
GenerateUniqueEmailsAsync Method
To generate a list of unique email addresses based on the provided base email, ensuring
that none of the suggested emails are already registered in the database or duplicated in
the list of suggestions.
Parameters:
 baseUsername: The initial username input from which to derive the
suggestions.
149

 count: Optional parameter that specifies the number of unique suggestions


to generate (defaults to 1 if not specified).
Logic:
 Split the Email: It divides the baseEmail into two parts: emailPrefix (before
the ‘@’) and emailDomain (after the ‘@’).
 Generate Suggestions: It iterates, creating new email addresses using the
prefix and a random number between 100 and 999, appended to the original
domain.
 Check Uniqueness: For each new suggestion, it checks asynchronously if
the email already exists in the database using AnyAsync(). It also checks if
the suggestion is already included in the list of generated suggestions to
avoid duplicates within the same batch.
 Compile Results: It continues generating suggestions until the required
number (count) of unique emails is achieved. Finally, it returns these
suggestions as a comma-separated string.
GenerateUniqueUsernamesAsync Method
To generate a list of unique usernames based on a provided base username, ensuring that
none of the suggested usernames are already in use in the database or repeated in the
generated list.
Parameters:
 baseUsername: The initial username input from which to derive the
suggestions.
 count: Optional parameter that specifies the number of unique suggestions
to generate (defaults to 1 if not specified).
Logic:
 Generate Suggestions: Similar to the email method. It loops to generate
new usernames by appending a random number between 100 and 999 to the
base username.
 Check Uniqueness: Each generated username is checked asynchronously
using AnyAsync() to ensure it does not exist in the database. It also checks
against the current list of generated usernames to ensure no internal
duplicates.
 Compile Results: The loop continues until the desired number of unique
usernames is reached. The method then returns these usernames as a
comma-separated string.
Registering the GenerateSuggestions Service:
Next, we need to register the GenerateSuggestions service into the dependency injection
container so that the MVC framework will inject the dependency object. So, please add the
following code to the Program.cs class file:
builder.Services.AddScoped<GenerateSuggestions>();
Creating a Remote Validation Controller
This controller will handle the remote validation checks. So, create an Empty MVC
Controller named RemoteValidationController within the Controller folder and copy and
paste the following code. The following IsEmailAvailable and IsUsernameAvailable methods
serve specific purposes for validating user input in real-time. These methods improve user
experience by quickly providing feedback on the availability of emails and usernames.
using Microsoft.AspNetCore.Mvc;
150

// Required for asynchronous LINQ methods like AnyAsync

using Microsoft.EntityFrameworkCore;

using RemoteValidationDemo.Models;

namespace RemoteValidationDemo.Controllers

public class RemoteValidationController : Controller

private readonly EFCoreDBContext _context;

private readonly GenerateSuggestions _generateSuggestions;

public RemoteValidationController(EFCoreDBContext context, GenerateSuggestions


generateSuggestions)

_context = context;

_generateSuggestions = generateSuggestions;

[AcceptVerbs("GET", "POST")]

public async Task<IActionResult> IsEmailAvailable(string email)

// Use AnyAsync for asynchronous existence check

if (!await _context.Users.AnyAsync(u => u.Email == email))

return Json(true);

//The second parameter will decide the number of unique suggestions to be generated

var suggestedEmails = await _generateSuggestions.GenerateUniqueEmailsAsync(email, 3);

return Json($"Email is already in use. Try anyone of these: {suggestedEmails}");


151

[AcceptVerbs("GET", "POST")]

public async Task<IActionResult> IsUsernameAvailable(string username)

// Use AnyAsync for asynchronous existence check

if (!await _context.Users.AnyAsync(u => u.UserName == username))

return Json(true);

//The second parameter will decide the number of unique suggestions to be generated

var suggestedUsernames = await


_generateSuggestions.GenerateUniqueUsernamesAsync(username, 3);

return Json($"Username is already taken. Try anyone of these: {suggestedUsernames}");

}
IsEmailAvailable Method
This method asynchronously checks if an email address is already registered in the
database and, if so, provides alternative suggestions. The method uses the AnyAsync
method to query the database asynchronously and determine if the given email already
exists in the Users table.
Provide Feedback:
 Email Available: If the email does not exist in the database, the method
returns a JSON object with a true value, indicating the email is available.
 Email Not Available: If the email exists, it triggers the
GenerateUniqueEmailsAsync method from the GenerateSuggestions class to
generate three alternative email suggestions. It then returns a JSON object
containing a message that the email is in use, along with the suggestions.
IsUsernameAvailable Method
Similar to the IsEmailAvailable method, but focused on checking the availability of
usernames. This method also employs AnyAsync to check asynchronously if the username
already exists in the database.
Provide Feedback:
 Username Available: If no existing user with the username is found, the
method returns a JSON object that is true, signaling that the username can
be used.
152

 Username Not Available: If the username is taken, the method utilizes the
GenerateUniqueUsernamesAsync to generate three alternative usernames. It
then returns a JSON response indicating the username is taken and provides
alternative suggestions.
How Do We Implement Remote Validation in ASP.NET Core MVC?
We need to use the Remote attribute in ASP.NET Core MVC to enable server-side
validation as part of the client-side validation process. This attribute makes an AJAX call to
a specified server method to validate the data entered by the user, providing an
asynchronous way to check for uniqueness or other custom conditions that cannot be easily
validated using standard client-side validation techniques. Here is how it works in ASP.NET
Core MVC.
 AJAX Call: When a user interacts with a form field (typically on losing focus),
the Remote attribute triggers an AJAX call to a specific server-side method.
 Server-Side Method: The server method specified by the attribute checks
the input against business rules or database constraints. It then returns a
validation response (true if valid, false if not).
 Feedback: If the response is false, an error message specified in the Remote
attribute is displayed to the user without the user needing to submit the form.
This instant feedback enhances the user experience by preventing form
submissions that would otherwise fail server-side validation.
Add Model with Validation Attributes
Next, we need to create a model with the necessary validation attributes and Apply the
Remote attribute to the property you want to validate. Please add the
following RegisterViewModel.cs class file within the Models folder and then copy and
paste the following code.
using Microsoft.AspNetCore.Mvc;

using System.ComponentModel.DataAnnotations;

namespace RemoteValidationDemo.Models

public class RegisterViewModel

[Required(ErrorMessage = "Email is Required")]

[EmailAddress(ErrorMessage = "Please Enter a Valid Email")]

[Remote(action: "IsEmailAvailable", controller: "RemoteValidation", ErrorMessage = "Email


is already in use. Please use a different email address.")]

public string Email { get; set; }

[Required(ErrorMessage = "User Name is Required")]


153

[MinLength(8, ErrorMessage = "Minimum 8 Characters Required")]

[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$])[A-Za-z\d@#$]{8,}$",
ErrorMessage = "Username must be at least 8 characters long and include lower case, upper
case, a digit, and a special character.")]

[Remote(action: "IsUsernameAvailable", controller: "RemoteValidation", ErrorMessage =


"Username is already taken. Please use a different username.")]

public string UserName { get; set; }

[Required(ErrorMessage = "Password is Required")]

public string Password { get; set; }

}
If you look at the above View Model, then you will see we have applied the Remote Attribute
with the Email and UserName property:
Email Property:
Remote(action: “IsEmailAvailable”, controller: “RemoteValidation”, ErrorMessage =
“Email is already in use. Please use a different email address.”)]
 Action: “IsEmailAvailable” – This is the server-side action method of the
RemoteValidation controller that the AJAX call will invoke.
 Controller: “RemoteValidation” – Specifies the controller where the
IsEmailAvailable action method is defined.
 ErrorMessage: If the IsEmailAvailable method returns false (indicating the
email is already in use), this error message is displayed: “Email is already in
use. Please use a different email address.”
UserName Property:
[Remote(action: “IsUsernameAvailable”, controller: “RemoteValidation”,
ErrorMessage = “Username is already taken. Please use a different username.”)]
 Action: “IsUsernameAvailable” – Similar to the email check, this specifies
the server-side method that checks if the username is available.
 Controller: “RemoteValidation” – This is the controller where the
IsUsernameAvailable action method resides.
 ErrorMessage: If the method returns false (indicating the username is
taken), this error message is shown: “Username is already taken. Please use
a different username.”
Create the Controller for Handling Form Submission
Next, we need to create a controller to handle the form submission. So, create an Empty
MVC Controller named UserController within the Controllers folder and copy and paste
the following code.
using Microsoft.AspNetCore.Mvc;
154

using RemoteValidationDemo.Models;

namespace RemoteValidationDemo.Controllers

public class UserController : Controller

private readonly EFCoreDBContext _context;

private readonly GenerateSuggestions _generateSuggestions;

public UserController(EFCoreDBContext context, GenerateSuggestions


generateSuggestions)

_context = context;

_generateSuggestions = generateSuggestions;

[HttpGet]

public ActionResult Create()

return View();

[HttpPost]

public async Task<IActionResult> Create(RegisterViewModel model)

if (ModelState.IsValid)

if (_context.Users.Any(u => u.Email == model.Email))


155

//The second parameter will decide the number of unique suggestions to be generated

var suggestedEmails = await


_generateSuggestions.GenerateUniqueEmailsAsync(model.UserName, 2);

//Dynamically Adding Model Validation Error incase JavaScript is Disabled

ModelState.AddModelError("Email", $"Email is already in use. Try anyone of these:


{suggestedEmails}");

if (_context.Users.Any(u => u.UserName == model.UserName))

//The second parameter will decide the number of unique suggestions to be generated

var suggestedUsername = await


_generateSuggestions.GenerateUniqueUsernamesAsync(model.UserName, 2);

//Dynamically Adding Model Validation Error incase JavaScript is Disabled

ModelState.AddModelError("UserName", $"Username is already taken. Try anyone of these:


{suggestedUsername}");

if (ModelState.IsValid)

// Proceed with saving the new user or any other business logic

User user = new User

Email = model.Email,

UserName = model.UserName,

Password = model.Password
156

};

await _context.Users.AddAsync(user);

await _context.SaveChangesAsync();

return RedirectToAction("Successful");

// Return the view with validation messages if any checks fail

return View(model);

public string Successful()

return "User Added Successfully";

}
In the above code, the GET version of the Create action method renders the view, which
will ask the user to enter the Email and User Name and the POST version of the Create
action method to handle the form submission. It might be possible that the client may
disable the JavaScript on his browser. In that case, the Remote Validation will not work. So,
for the safer side, we are also handling the User Name and Email Validation manually
inside the POST version of the Create action method.
Creating the View:
Next, add a view named Create.cshtml within the Views/User folder and copy and paste
the following. This view will ask the user to enter the User Name, Email, and Password.
@model RemoteValidationDemo.Models.RegisterViewModel

@{

ViewData["Title"] = "Create User";

}
157

<h1>Create User</h1>

<div class="container mt-4">

<form asp-controller="User" asp-action="Create" method="Post">

<div asp-validation-summary="All" class="text-danger mb-3"></div>

<div class="form-group row mb-3">

<label asp-for="Email" class="col-sm-2 col-form-label">Email</label>

<div class="col-sm-10">

<input asp-for="Email" class="form-control" placeholder="Enter your email">

<span asp-validation-for="Email" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="UserName" class="col-sm-2 col-form-label">Username</label>

<div class="col-sm-10">

<input asp-for="UserName" class="form-control" placeholder="Enter your username">

<span asp-validation-for="UserName" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="Password" class="col-sm-2 col-form-label">Password</label>

<div class="col-sm-10">

<input asp-for="Password" type="password" class="form-control" placeholder="Enter your


password">

<span asp-validation-for="Password" class="text-danger"></span>


158

</div>

</div>

<div class="form-group row mb-3">

<div class="offset-sm-2 col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
Testing the Application:
Now, run the application, navigate to the User/Create URL, and try to enter the Email and
User Name that exists in the database. You should see the Error Message immediately, as
shown in the below image:
159

Creating a Custom Remote Attribute for Server-Side Validation:


If you look at the Controller Code, we have implemented the required Validation logic for the
Email and User Name for Server-Side Validation. However, delegating the responsibility of
performing validation to a controller action method violates the separation of concerns
within MVC. Ideally, all validation logic should be in the Model. Using validation attributes in
models should be the preferred method for validation. Let us proceed and see how we can
create a Custom Validation Attribute for the Same.
Creating custom validation attributes in ASP.NET Core MVC allows us to encapsulate the
validation logic and reuse it across different parts of our application. This approach
enhances the separation of concerns and adheres to the DRY (Don’t Repeat Yourself)
principle. Here, we’ll create two custom validation attributes: one for the email and another
for the username. These attributes will check for uniqueness in the database and suggest
alternatives when the provided values are already in use.
Email Validation Attribute
So, create a class file named UniqueEmailAttribute.cs within the Models folder and copy
and paste the following code. As you can see, the class is inheriting from the
ValidationAttribute class and overriding the IsValid method.
using System.ComponentModel.DataAnnotations;

namespace RemoteValidationDemo.Models

public class UniqueEmailAttribute : ValidationAttribute

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)
160

//Get the EF Core DB Context Instance

var context = validationContext.GetService<EFCoreDBContext>();

var email = value as string;

if (context.Users.Any(u => u.Email == email))

//Get the GenerateSuggestions Instance

var generateSuggestions = validationContext.GetService<GenerateSuggestions>();

//The second parameter will decide the number of unique suggestions to be generated

var suggestedEmails = generateSuggestions?.GenerateUniqueEmailsAsync(email, 3).Result;

return new ValidationResult($"Email is already in use. Try anyone of these:


{suggestedEmails}");

return ValidationResult.Success;

}
User Name Validation Attribute
So, create a class file named UniqueUsernameAttribute.cs within the Models folder and
copy and paste the following code. As you can see, the class is inheriting from the
ValidationAttribute class and overriding the IsValid method.
using System.ComponentModel.DataAnnotations;

namespace RemoteValidationDemo.Models

public class UniqueUsernameAttribute :ValidationAttribute

{
161

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

//Get the EF Core DB Context Instance

var context = validationContext.GetService<EFCoreDBContext>();

var userName = value as string;

if (context.Users.Any(u => u.UserName == userName))

//Get the GenerateSuggestions Instance

var generateSuggestions = validationContext.GetService<GenerateSuggestions>();

//The second parameter will decide the number of unique suggestions to be generated

var suggestedUsernames =
generateSuggestions?.GenerateUniqueUsernamesAsync(userName, 3).Result;

return new ValidationResult($"Username is already taken. Try anyone of these:


{suggestedUsernames}");

return ValidationResult.Success;

}
Apply Custom Validation Attributes to Your Model
Now, apply these custom attributes to your model properties and other built-in and remote
attributes. So, please modify the RegisterViewModel.cs class file as follows.
using Microsoft.AspNetCore.Mvc;

using System.ComponentModel.DataAnnotations;

namespace RemoteValidationDemo.Models
162

public class RegisterViewModel

[Required(ErrorMessage = "Email is Required")]

[EmailAddress(ErrorMessage = "Please Enter a Valid Email")]

[Remote(action: "IsEmailAvailable", controller: "RemoteValidation", ErrorMessage = "Email


is already in use. Please use a different email address.")]

[UniqueEmail]

public string Email { get; set; }

[Required(ErrorMessage = "User Name is Required")]

[MinLength(8, ErrorMessage = "Minimum 8 Characters Required")]

[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$])[A-Za-z\d@#$]{8,}$",
ErrorMessage = "Username must be at least 8 characters long and include lower case, upper
case, a digit, and a special character.")]

[Remote(action: "IsUsernameAvailable", controller: "RemoteValidation", ErrorMessage =


"Username is already taken. Please use a different username.")]

[UniqueUsername]

public string UserName { get; set; }

[Required(ErrorMessage = "Password is Required")]

public string Password { get; set; }

}
Modifying the Controller:
With the Custom Validation Attribute applied to Model Properties which will handle the
Server-Side Validation, now, we can safely remove the Validation Logic from the Controller
action method. So, please modify the User Controller as follows:
using Microsoft.AspNetCore.Mvc;
163

using RemoteValidationDemo.Models;

namespace RemoteValidationDemo.Controllers

public class UserController : Controller

private readonly EFCoreDBContext _context;

public UserController(EFCoreDBContext context, GenerateSuggestions


generateSuggestions)

_context = context;

[HttpGet]

public ActionResult Create()

return View();

[HttpPost]

public async Task<IActionResult> Create(RegisterViewModel model)

if (ModelState.IsValid)

if (ModelState.IsValid)

// Proceed with saving the new user or any other business logic
164

User user = new User

Email = model.Email,

UserName = model.UserName,

Password = model.Password

};

await _context.Users.AddAsync(user);

await _context.SaveChangesAsync();

return RedirectToAction("Successful");

// Return the view with validation messages if any checks fail

return View(model);

public string Successful()

return "User Added Successfully";

}
Testing the Application:
Before testing the Application to check whether the server-side validations are working fine
or not, first, disable the Browser JavaScript and then provide the value that exists in the
database. This time, you will see that client-side validation is not working, as shown in the
below image:
165

Now, with the above-existing Email and User Name, submit the data to the server. On the
server side, the validation will check, and then it will give you the error as shown in the
below image:

Removing the Suggestions:


Sometimes, your business might require you not to provide suggestions to the user. In that
case, we need to remove the code from our application that provides the suggestions. Let
us do this.
First, modify the RemoteValidationController as follows:
using Microsoft.AspNetCore.Mvc;

// Required for asynchronous LINQ methods like AnyAsync

using Microsoft.EntityFrameworkCore;

using RemoteValidationDemo.Models;
166

namespace RemoteValidationDemo.Controllers

public class RemoteValidationController : Controller

private readonly EFCoreDBContext _context;

public RemoteValidationController(EFCoreDBContext context, GenerateSuggestions


generateSuggestions)

_context = context;

[AcceptVerbs("GET", "POST")]

public async Task<IActionResult> IsEmailAvailable(string email)

// Use AnyAsync for asynchronous existence check

if (!await _context.Users.AnyAsync(u => u.Email == email))

return Json(true);

return Json($"Email is already in use.");

[AcceptVerbs("GET", "POST")]

public async Task<IActionResult> IsUsernameAvailable(string username)

// Use AnyAsync for asynchronous existence check

if (!await _context.Users.AnyAsync(u => u.UserName == username))

return Json(true);
167

return Json($"Username is already taken");

}
Then modify the UniqueEmailAttribute as follows:
using System.ComponentModel.DataAnnotations;

namespace RemoteValidationDemo.Models

public class UniqueEmailAttribute : ValidationAttribute

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

//Get the EF Core DB Context Instance

var context = validationContext.GetService<EFCoreDBContext>();

var email = value as string;

if (context.Users.Any(u => u.Email == email))

//Get the GenerateSuggestions Instance

var generateSuggestions = validationContext.GetService<GenerateSuggestions>();

return new ValidationResult($"Email is already in use");

return ValidationResult.Success;

}
168

}
Finally, modify the UniqueUsernameAttribute as follows:
using System.ComponentModel.DataAnnotations;

namespace RemoteValidationDemo.Models

public class UniqueUsernameAttribute :ValidationAttribute

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

//Get the EF Core DB Context Instance

var context = validationContext.GetService<EFCoreDBContext>();

var userName = value as string;

if (context.Users.Any(u => u.UserName == userName))

//Get the GenerateSuggestions Instance

var generateSuggestions = validationContext.GetService<GenerateSuggestions>();

return new ValidationResult($"Username is already taken");

return ValidationResult.Success;

}
169

With the above changes in place, run the application, and you should see the following error
message. You can test by enabling and disabling JavaScript.

Real-Time Examples of Remote Validations in ASP.NET Core MVC


The following are some of the typical scenarios where you might consider using remote
validation:
 Unique Field Validation: When you need to ensure that a field’s value is
unique, such as a username or email address. Remote validation can be
used to check the existing database and confirm that the entered value has
not already been taken.
 Complex Business Rules: Remote validation can be applied if a field’s
validity depends on complex logic that requires database access or server-
side resources.
 Dynamic Data Verification: For validating data that is frequently updated
and must be verified against the latest server data. For example, checking the
availability of a product or a booking slot in real-time.

Blacklist and Whitelist Checks using Data Annotation in ASP.NET Core MVC
In this article, I will discuss Blacklist and Whitelist Checks using Data Annotation in
ASP.NET Core MVC Applications. Please read our previous article discussing Remote
Validations in ASP.NET Core MVC.
Blacklist and Whitelist Checks using Data Annotation in ASP.NET Core
MVC
In ASP.NET Core MVC, implementing blacklist and whitelist checks is one of the security
measures to control access to resources and to filter inputs based on defined lists of
allowed (whitelist) or disallowed (blacklist) entities. This can help prevent unauthorized
access, SQL injection, cross-site scripting (XSS), and other malicious activities.
 Whitelist: Only allows items that appear on the list. It’s generally considered
more secure than a blacklist because it explicitly permits only known safe
entities.
170

 Blacklist: Blocks items that appear on the list. It can be harder to manage
because it requires anticipating all possible harmful inputs.
Both blacklisting and whitelisting are methods used to filter and validate data. Blacklisting
involves blocking specific values, while whitelisting permits only specific values.
Create Custom Data Annotations
ASP.NET Core does not provide built-in annotations for blacklist and whitelist checks, so we
need to create Custom Data Annotation Attributes. Let’s create custom data annotation
attributes in ASP.NET Core MVC for Black and White Lists.

Traveling across Quang Binh to stunning cinematic locations00:11 / 03:1710 Sec

Whitelist Attribute:
This attribute will only allow specific values from a predefined list. So, first, create a custom
data annotation attribute named WhitelistAttribute.cs within the Models folder and then
copy and paste the following code into it.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class WhitelistAttribute : ValidationAttribute

public string[] AllowedValues { get; set; }

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

if (value != null && AllowedValues.Contains(value.ToString(),


StringComparer.OrdinalIgnoreCase))

return ValidationResult.Success;

return new ValidationResult($"The value {value} is not allowed.");

}
171

}
Blacklist Attribute:
This attribute will block specific values from a predefined list. So, create a custom data
annotation attribute named BlackListAttribute.cs within the Models folder and copy and
paste the following code into it.
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class BlacklistAttribute : ValidationAttribute

public string[] DisallowedValues { get; set; }

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

var email = value as string;

if (string.IsNullOrEmpty(email))

return ValidationResult.Success;

var domain = email.Split('@').LastOrDefault();

if (domain != null && DisallowedValues.Contains(domain,


StringComparer.OrdinalIgnoreCase))

return new ValidationResult($"The domain {domain} is not allowed.");

return ValidationResult.Success;
172

}
Define Your Model
Now, we need to create a model class to restrict certain inputs by applying the above
WhitelistAttribute and BlackListAttribute. So, create a class file named UserInput.cs and
copy and paste the following code. Apply the custom annotations to the appropriate
properties in your model.
namespace DataAnnotationsDemo.Models

public class UserInput

public int Id { get; set; }

[Whitelist(AllowedValues = new string[] { "Admin", "User", "Guest" })]

public string Role { get; set; }

[Blacklist(DisallowedValues = new string[] { "example.com", "test.com" })]

public string EmailDomain { get; set; }

}
Handling Validation in the Controller
In your controller, you can check the validation status of your model as part of your action
methods. Next, modify the Home Controller as follows:
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class HomeController : Controller

{
173

public ActionResult Create()

return View();

[HttpPost]

public ActionResult Create(UserInput model)

//Check if the Model State is Valid

if (ModelState.IsValid)

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");

//Return to the same View and Display Model Validation error

return View(model);

public string Successful()

return "Role Added Successfully";

}
Create a View:
174

Ensure your views appropriately display validation messages. Next, create a view
named Create.cshtml within the Views/Home folder and then copy and paste the following
code:
@model DataAnnotationsDemo.Models.UserInput

@{

ViewData["Title"] = "Create";

<div class="row">

<form asp-controller="Home" asp-action="Create" method="Post" class="mt-3">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group row mb-3">

<label asp-for="Role" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="Role" class="form-control">

<span asp-validation-for="Role" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="EmailDomain" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="EmailDomain" class="form-control">

<span asp-validation-for="EmailDomain" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">


175

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
Now, run the application and see if everything is working as expected. If you provide the
wrong value, then you will get an error message, as shown in the image below:

Real-Time Example: Comment Filtering for a Blog Post using


Black List and White List
Let’s create a complete example in an ASP.NET Core MVC project where we implement
server-side validation to restrict comments on a blog post if they contain certain offensive
words.
Create Custom Data Annotations
First, create the OffensiveWordBlacklist Attribute that inherits from ValidationAttribute. This
attribute will check if the comment contains any words from a predefined list of offensive
words. So, create a class file named OffensiveWordBlacklistAttribute.cs and then copy
and paste the following code:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class OffensiveWordBlacklistAttribute : ValidationAttribute

{
176

public string[] DisallowedWords { get; set; }

protected override ValidationResult IsValid(object value, ValidationContext


validationContext)

if (value is string commentText)

var foundWords = new List<string>();

// Check each word in the list of disallowed words

foreach (var word in DisallowedWords)

// Case insensitive check if the comment contains the disallowed word

if (commentText.Contains(word, StringComparison.OrdinalIgnoreCase))

foundWords.Add(word);

// If any offensive words are found, return a ValidationResult with all offending words

if (foundWords.Any())

return new ValidationResult($"The comment contains disallowed words: {string.Join(", ",


foundWords)}");

return ValidationResult.Success;
177

}
Explanation:
 List of Found Words: The foundWords list collects all the offensive words
that appear in the comment.
 Check Each Word: The loop checks each word in the DisallowedWords
array to see if it appears in the comment.
 Record Offenses: If a word is found, it’s added to the foundWords list.
 Validation Result: If any offensive words are found, a ValidationResult is
returned that lists all of these words. If none are found,
ValidationResult.Success is returned
Create the Model
Next, we will define the BlogComment model, which represents a user’s comment on a blog
post. This model will include the comment text and a custom validation attribute to check for
offensive words. So, create a class file named BlogComment.cs and then copy and paste
the following code:
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class BlogComment

[Required]

[EmailAddress]

public string UserEmail { get; set; }

[Required]

public string UserName { get; set; }

[Required]

[OffensiveWordBlacklist(DisallowedWords = new string[] { "offensive1", "offensive2",


"offensive3" })]

public string CommentText { get; set; }


178

}
Create the MVC Controller
Now, let’s implement a controller with actions to create and post comments. The Create
GET action displays the form, and the Create POST action handles the form submission.
So, create an MVC Empty Controller named BlogCommentController within the
Controllers folder and then copy and paste the following code:
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class BlogCommentController : Controller

[HttpGet]

public IActionResult Create()

return View();

[HttpPost]

public IActionResult Create(BlogComment blogComment)

if (ModelState.IsValid)

//Save the Data into the Database

//Redirect to a Different View

return RedirectToAction("Successful");
179

// If validation fails, show form again with validation messages

return View(blogComment);

public string Successful()

return "Comment Posted Successfully!";

}
Create the View
Next, create a view named Create.cshtml within the Views/BlogComment folder, and then
copy and paste the following code:
@model DataAnnotationsDemo.Models.BlogComment

@{

ViewData["Title"] = "Blogpost Comment";

<div class="row">

<form asp-controller="BlogComment" asp-action="Create" method="Post" class="mt-3">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group row mb-3">

<label asp-for="UserName" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="UserName" class="form-control">

<span asp-validation-for="UserName" class="text-danger"></span>


180

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="UserEmail" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<input asp-for="UserEmail" class="form-control">

<span asp-validation-for="UserEmail" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="CommentText" class="col-sm-2 col-form-label"></label>

<div class="col-sm-10">

<textarea asp-for="CommentText" class="form-control"></textarea>

<span asp-validation-for="CommentText" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">Create</button>

</div>

</div>

</form>

</div>
181

With the above changes in place, run the application and navigate
to BlogComment/Create URL. Fill out the form with offensive words in the comment
section, and then click on submit. The application will then process the comment on the
server side and return it with the error message shown in the image below.

Displaying and Formatting Attributes in ASP.NET Core MVC


In this article, I will discuss Displaying and Formatting Data Annotation Attributes in
ASP.NET Core MVC Applications with Examples. Please read our previous article
discussing Blacklist and Whitelist Checks using Data Annotation in ASP.NET Core
MVC. We are provided with Display, DisplayName, and DisplayFormat Attribute for
displaying and formatting purposes, which we can apply to the Model properties. To take
this effect, we need to use DisplayFor and DisplayNameFor HTML Helper methods on
views.
DisplayFor and DisplayNameFor in ASP.NET Core MVC
In ASP.NET Core MVC, DisplayFor and DisplayNameFor are HTML helper methods used
to display model properties in a view. These helpers are part of the Razor view engine and
help render data while respecting the data annotations applied to model properties. They
are commonly used in forms and data display scenarios.
DisplayNameFor HTML Helper Method in ASP.NET Core MVC
The DisplayNameFor helper is used to display the name of a model property. This is
particularly useful when you want to keep your views clean and consistent with the model’s
metadata. If you have annotated your model property with the DisplayName or Display
attributes, DisplayNameFor will render the value specified in these attributes as the field
name.
using System.ComponentModel;

using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models
182

public class Employee

[Display(Name = "Fist Name")]

public string FirstName { get; set; }

[DisplayName("Email Address")]

public string Email { get; set; }

[Display(Name = "Date Of Birth")]

public DateTime DateOfBirth { get; set; }

}
DisplayFor HTML Helper Method in ASP.NET Core MVC
The DisplayFor helper method formatted the value of a model property, taking into account
any formatting rules or display templates defined for that property. If the property has
display attributes like DisplayFormat, DisplayFor respects these settings when rendering the
property’s value.

Traveling across Quang Binh to stunning cinematic locations00:11 / 03:1710 Sec

using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

//{0:C} formats the Salary

[DisplayFormat(DataFormatString = "{0:C}", ApplyFormatInEditMode = true)]

public decimal Salary { get; set; }

//{0:N2} formats the PerformanceRating with two decimal places


183

[DisplayFormat(DataFormatString = "{0:N2}", ApplyFormatInEditMode = true)]

public double PerformanceRating { get; set; }

//Format a date as "Year-Month-Day"

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

public DateTime DateOfBirth { get; set; }

//Display message when the string is null or empty

[DisplayFormat(NullDisplayText = "No Description Available.")]

public string? JobDescription { get; set; }

}
Example to Understand DisplayFor, DisplayNameFor in ASP.NET Core
MVC:
Let us see one real-time application to understand the need and use of DisplayFor and
DisplayNameFor HTML Helper methods in ASP.NET Core MVC.
Creating Model:
First, create a class file named Employee.cs and copy and paste the following code into it.
This will be our model class, which will hold the employee data we want to display to the
user.
namespace DataAnnotationsDemo.Models

public class Employee

public int Id { get; set; }

public string FullName { get; set; }

public string Gender { get; set; }

public int Age { get; set; }

public DateTime DateOFJoining { get; set; }


184

public string EmailAddress { get; set; }

public decimal Salary { get; set; }

public string? PersonalWebSite { get; set; }

public double PerformanceRating { get; set; }

public DateTime DateOfBirth { get; set; }

public string? JobDescription { get; set; }

}
Creating Employee Controller:
Create an MVC Empty controller named EmployeeController within the Controllers folder
and copy and paste the following code. Here, we have hardcoded the employee details. But
in real time, you will get the details from the database.
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class EmployeeController : Controller

public ActionResult Details()

Employee employee = new Employee()

Id = 1,

FullName = "Pranaya Rout",

Gender = "Male",

Age = 29,
185

DateOFJoining = Convert.ToDateTime("2017-01-02"), //YYYY-DD-MM

DateOfBirth = Convert.ToDateTime("1988-02-29"), //YYYY-DD-MM

EmailAddress = "info@dotnettutorials.net",

Salary = 55000,

PersonalWebSite = "https://dotnettutorials.net/",

JobDescription = string.Empty,

PerformanceRating = 4.5,

LastDateOfWorking = null

};

return View(employee);

}
Creating Details View:
Add a view named Details.cshtml within the Views/Employee folder and copy and paste
the following code into it. For the following view, Employee is the model, and then using the
DisplayNameFor and DisplayFor HTML helper methods, we display the Employee details.
@model DataAnnotationsDemo.Models.Employee

@{

ViewData["Title"] = "Employee Details";

<div>

<h3>Employee Details</h3>

<hr />

<dl class="row">

<dt class = "col-sm-2">


186

@Html.DisplayNameFor(model => model.Id)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Id)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.FullName)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.FullName)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Gender)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Gender)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Age)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Age)

</dd>

<dt class = "col-sm-2">


187

@Html.DisplayNameFor(model => model.DateOFJoining)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.DateOFJoining)

</dd>

<dt class="col-sm-2">

@Html.DisplayNameFor(model => model.LastDateOfWorking)

</dt>

<dd class="col-sm-10">

@Html.DisplayFor(model => model.LastDateOfWorking)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.EmailAddress)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.EmailAddress)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Salary)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Salary)

</dd>

<dt class = "col-sm-2">


188

@Html.DisplayNameFor(model => model.PersonalWebSite)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.PersonalWebSite)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.PerformanceRating)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.PerformanceRating)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.DateOfBirth)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.DateOfBirth)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.JobDescription)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.JobDescription)

</dd>

</dl>
189

</div>
With the above changes in place, run the application and visit the Employee/Details URL.
You should see the employee details in the image below.

Notice that the output is not that pretty. We can control the display of data in a view using
display attributes that are present in
the System.ComponentModel and System.ComponentModel.DataAnnotations namesp
ace. So, modify the Employee.cs class file as follows:
using System.ComponentModel;

using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models

public class Employee

public int Id { get; set; }


190

[DisplayName("Full Name")]

public string FullName { get; set; }

[DisplayFormat(NullDisplayText = "Gender Not Specified")]

public string Gender { get; set; }

public int Age { get; set; }

[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]

public DateTime DateOFJoining { get; set; }

[DisplayName("Email Address")]

[DataType(DataType.EmailAddress)]

public string EmailAddress { get; set; }

[DisplayFormat(DataFormatString = "{0:C}", ApplyFormatInEditMode = true)]

public decimal Salary { get; set; }

[DisplayName("Personal WebSite")]

[DataType(DataType.Url)]

public string? PersonalWebSite { get; set; }

[DisplayFormat(DataFormatString = "{0:N2}", ApplyFormatInEditMode = true)]

public double PerformanceRating { get; set; }

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

public DateTime DateOfBirth { get; set; }

[DisplayFormat(NullDisplayText = "N/A")]

public string? JobDescription { get; set; }

[DisplayFormat(DataFormatString = "{0:MMM dd, yyyy}", NullDisplayText = "N/A",


ApplyFormatInEditMode = true)]

public DateTime? LastDateOfWorking { get; set; }


191

}
Explanation of Each Property:
Each attribute affects how the property will be displayed or edited in the user interface (UI).
Let us understand what we applied on each property of the above Employee model one by
one, and they are going to be displayed in the UI:
Id
 Property Type: int
 Display/Format Attributes: None applied.
 UI Display: Shows as a simple integer. No specific formatting or label
customization.
FullName
 Property Type: string
 Display Attributes: [DisplayName(“Full Name”)]
 UI Display: This attribute changes the property’s display name in the UI to
“Full Name” instead of the default property name, “FullName.”
Gender
 Property Type: string
 Display Attributes: [DisplayFormat(NullDisplayText = “Gender Not
Specified”)]
 UI Display: If Gender is null, it will display “Gender Not Specified”. Otherwise,
it displays the actual value of the Gender property.
Age
 Property Type: int
 Display/Format Attributes: None applied.
 UI Display: Displays as a simple integer.
DateOFJoining
 Property Type: DateTime
 Display Attributes: [DisplayFormat(DataFormatString = “{0:dd/MM/yyyy}”,
ApplyFormatInEditMode = true)]
 UI Display: Formats the date as “dd/MM/yyyy” (e.g., “23/04/2024”). This
format is also applied when editing the date in the UI.
EmailAddress
 Property Type: string
 Display Attributes: [DisplayName(“Email Address”)],
[DataType(DataType.EmailAddress)]
 UI Display: The label will read “Email Address” instead of “EmailAddress”.
The DataType attribute informs the UI that this string should be treated as an
email address, which can affect the rendering (e.g., may use <a href=”mailto:
…”>).
Salary
 Property Type: decimal
 Display Attributes: [DisplayFormat(DataFormatString = “{0:C}”,
ApplyFormatInEditMode = true)]
 UI Display: Formats the salary as a currency value (e.g., “$10,000.00”). This
currency formatting is applied in both display and edit modes.
192

PersonalWebSite
 Property Type: string?
 Display Attributes: [DisplayName(“Personal WebSite”)],
[DataType(DataType.Url)]
 UI Display: The label changes to “Personal WebSite”. The DataType attribute
specifies that this string should be formatted as a URL, potentially rendering it
as a clickable hyperlink.
PerformanceRating
 Property Type: double
 Display Attributes: [DisplayFormat(DataFormatString = “{0:N2}”,
ApplyFormatInEditMode = true)]
 UI Display: Formats the number to two decimal places (e.g., “85.75”). This
formatting is consistent in both display and edit modes.
DateOfBirth
 Property Type: DateTime
 Display Attributes: [DisplayFormat(DataFormatString = “{0:yyyy-MM-dd}”,
ApplyFormatInEditMode = true)]
 UI Display: Formats the date as “yyyy-MM-dd” (e.g., “1990-05-21”). This
setting is also applied in edit mode.
JobDescription
 Property Type: string?
 Display Attributes: [DisplayFormat(NullDisplayText = “N/A”)]
 UI Display: If JobDescription is null, the UI will display “N/A”. Otherwise, it
displays the actual value.
LastDateOfWorking
 Property Type: DateTime?
 Display Attributes: [DisplayFormat(DataFormatString = “{0:MMM dd, yyyy}”,
NullDisplayText = “N/A”, ApplyFormatInEditMode = true)]
 UI Display: For non-null dates, it formats them as “MMM dd, yyyy” (e.g., “Apr
23, 2024”). If null, it displays “N/A”. This formatting is used both for display
and edit modes.
Modifying the Program Class:
We need to set some settings in the program class to display the currency symbol specific
to culture. By default, as I am from India, the currency will be the Indian rupee. However, we
can also set the currency we want to display, such as the US dollar, the Great British
Pound, etc. To do so, please modify the Program class as shown below.
using Microsoft.AspNetCore.Localization;

namespace DataAnnotationsDemo

public class Program

public static void Main(string[] args)


193

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

//Configuring the Default Currency to be used in our application

builder.Services.Configure<RequestLocalizationOptions>(options =>

// options.DefaultRequestCulture = new RequestCulture("en-IN");

// options.DefaultRequestCulture = new RequestCulture("en-GB");

options.DefaultRequestCulture = new RequestCulture("en-US");

});

var app = builder.Build();

//It will automatically set culture information for requests

//based on information provided by the client.

app.UseRequestLocalization();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

app.UseExceptionHandler("/Home/Error");

// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.

app.UseHsts();

app.UseHttpsRedirection();
194

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Employee}/{action=Details}/{id?}");

app.Run();

}
After making the above changes, run the application, and you should get the expected
output, as shown in the image below.
195
196

BindNever and BindRequired Attribute in ASP.NET Core MVC


In this article, I will discuss BindNever and BindRequired Attribute in ASP.NET Core
MVC Applications with Examples. Please read our previous article discussing Displaying
and Formatting Data Annotation Attributes in ASP.NET Core MVC Applications.
In ASP.NET Core MVC, the BindNever and BindRequired attributes are used to control
model binding behavior for specific properties on model classes. These attributes ensure
that our application handles input data correctly, which is important for maintaining the
integrity and security of the data.
BindNever Attribute in ASP.NET Core MVC
The BindNever attribute is used to explicitly prevent a property from being bound by model
binding. This is useful in scenarios where you do not want certain data to be updated from
the request, such as properties that are set programmatically and should not be changed by
user input. For instance, an Id or CreatedBy field should not be altered once the object is
created. Here’s how you might use the BindNever attribute:
using Microsoft.AspNetCore.Mvc.ModelBinding;

public class Product

public int Id { get; set; }

[BindNever]

public string CreatedBy { get; set; } // This property will not be bound to user input

public string Name { get; set; }

public decimal Price { get; set; }

}
In this example, the CreatedBy property will not receive any data from an incoming request,
regardless of whether it appears in the request. This helps protect critical fields that should
not be altered directly by external users.
Ad
1/2
00:14

Traveling across Quang Binh to stunning cinematic locations


When to use BindNever:
 Security: To ensure that critical properties, like IDs or user roles, are not
changed by the end user.
 Integrity: To maintain the integrity of data that should not be altered after its
initial creation.
197

 Control: To explicitly control which properties should be excluded from the


binding process.
BindRequired Attribute in ASP.NET Core MVC
The BindRequired attribute, on the other hand, specifies that a property must be present in
the input data of a request; otherwise, model binding will fail. This attribute is useful for
ensuring that necessary fields are not omitted when data is submitted. Example of using the
BindRequired attribute:
public class RegistrationViewModel

[BindRequired]

public string Username { get; set; }

[BindRequired]

public string Email { get; set; }

public string Password { get; set; }

}
In this example, both Username and Email must be present in the request data; otherwise,
the model binding will fail, making this method effective for enforcing mandatory fields.
When to use BindRequired:
 Validation: Ensure the user provides essential fields during form submissions
or API calls.
 Data Integrity: To prevent the application from processing incomplete or
corrupt data.
 User Feedback: To generate automatic validation responses that inform the
user of required data that was not submitted.
Example of BindNever and BindRequired Attribute in ASP.NET
Core MVC:
Suppose you build an e-commerce application using ASP.NET Core MVC with an Order
model. The Order model includes sensitive information that should not be updated directly
from form data, such as OrderTotal and PaymentProcessed. Other fields like
CustomerName, OrderDate, and ShippingAddress should also be updated. So, create a
class file named Order.cs and copy and paste the following code. Inside this file, we are
creating all our models.
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace DataAnnotationsDemo.Models

public class Product


198

public int ProductId { get; set; }

public string Name { get; set; }

public decimal Price { get; set; }

public class OrderItem

public int ProductId { get; set; } // Foreign key to Product

public int Quantity { get; set; }

public decimal Price { get; set; } // This can be the selling price which might include
discounts etc.

[BindNever]

public Product? Product { get; set; } // Navigation property to Product

public class Order

public int OrderId { get; set; }

[BindRequired]

public string CustomerName { get; set; }

[BindRequired]

public DateTime OrderDate { get; set; }

[BindNever]

public decimal OrderTotal { get; set; }

[BindRequired]
199

public string ShippingAddress { get; set; }

public string PhoneNumber { get; set; }

[BindNever]

public bool PaymentProcessed { get; set; }

public List<OrderItem> Items { get; set; }

}
In the Order model:
 ShippingAddress is a required field as it’s crucial for processing the delivery.
 PhoneNumber is optional but can be useful for contacting the customer
regarding their order.
 PaymentProcessed is marked with BindNever to ensure it’s not modified
directly by form submission, reflecting whether payment has been securely
processed on the server side.
 Items represent a list of OrderItem, detailing the items included in the order.
Add a Controller for the Order
Create a new Empty MVC Controller in the Controllers folder
called OrderController.cs, and then copy and paste the following code.
using DataAnnotationsDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace DataAnnotationsDemo.Controllers

public class OrderController : Controller

private List<Product> listProducts;

public OrderController()

listProducts = new List<Product>()

{
200

new Product { ProductId = 1, Name = "Laptop", Price = 999.99m },

new Product { ProductId = 3, Name = "Mobile", Price = 300.50m },

new Product { ProductId = 4, Name = "Desktop", Price = 1330.50m }

};

[HttpGet]

public IActionResult Create()

var order = new Order

OrderDate = DateTime.Today,

Items = listProducts.Select(p => new OrderItem

ProductId = p.ProductId,

Quantity = 0, // Initialize with zero, let user choose the amount

Price = p.Price,

Product = p // Link the product details directly

}).ToList()

};

return View(order);

[HttpPost]

public IActionResult Create(Order order)

{
201

if (ModelState.IsValid)

// Reapply correct prices and link products to guard against tampering

foreach (var item in order.Items)

var product = listProducts.FirstOrDefault(p => p.ProductId == item.ProductId);

if (product != null)

item.Price = product.Price;

item.Product = product;

order.OrderTotal = CalculateOrderTotal(order);

order.PaymentProcessed = ProcessPayment(order);

return RedirectToAction("Success");

// Ensure products are linked if returning to view after a failed validation

foreach (var item in order.Items)

item.Product = listProducts.FirstOrDefault(p => p.ProductId == item.ProductId);

return View(order);

private decimal CalculateOrderTotal(Order order)


202

decimal total = 0m;

foreach (var item in order.Items)

total += item.Price * item.Quantity;

return total;

private bool ProcessPayment(Order order)

// Assume payment processing always succeeds for this example

return true;

public IActionResult Success()

return View();

}
Create the View
Create a new View named Create.cshtml in the Views/Order folder, and then copy and
paste the following code.
@model DataAnnotationsDemo.Models.Order

@{

ViewData["Title"] = "Place Order";


203

<h2>Create Order</h2>

<form asp-controller="Order" asp-action="Create" method="Post">

<div asp-validation-summary="All" class="text-danger mb-3"></div>

<div class="form-group row mb-3">

<label asp-for="CustomerName" class="col-sm-2 col-form-label">Customer Name:</label>

<div class="col-sm-10">

<input asp-for="CustomerName" class="form-control">

<span asp-validation-for="CustomerName" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="OrderDate" class="col-sm-2 col-form-label">Order Date:</label>

<div class="col-sm-10">

<input asp-for="OrderDate" type="date" class="form-control">

<span asp-validation-for="OrderDate" class="text-danger"></span>

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="ShippingAddress" class="col-sm-2 col-form-label">Shipping


Address:</label>

<div class="col-sm-10">

<input asp-for="ShippingAddress" class="form-control">

<span asp-validation-for="ShippingAddress" class="text-danger"></span>


204

</div>

</div>

<div class="form-group row mb-3">

<label asp-for="PhoneNumber" class="col-sm-2 col-form-label">Phone Number:</label>

<div class="col-sm-10">

<input asp-for="PhoneNumber" class="form-control">

</div>

</div>

<h3>Order Items</h3>

<table class="table table-bordered">

<colgroup>

<col style="width: 25%;">

<col style="width: 25%;">

<col style="width: 25%;">

<col style="width: 25%;">

</colgroup>

<thead>

<tr>

<th>Item Name</th>

<th>Quantity</th>

<th>Unit Price</th>

<th>Total Price</th>

</tr>

</thead>
205

<tbody>

@for (int i = 0; i < Model.Items.Count; i++)

<tr>

<td>

<span>@Model.Items[i].Product?.Name</span>

<input type="hidden" asp-for="@Model.Items[i].ProductId" />

</td>

<td>

<input asp-for="@Model.Items[i].Quantity" type="number" class="form-control quantity"


/>

</td>

<td class="text-right">

<span>@Model.Items[i].Price.ToString("C")</span>

<input type="hidden" asp-for="@Model.Items[i].Price" class="unit-price" />

</td>

<td class="text-right">

<span class="total-price">@((Model.Items[i].Price *
Model.Items[i].Quantity).ToString("C"))</span>

</td>

</tr>

</tbody>

</table>
206

<div class="form-group row">

<div class="col-sm-12 d-flex justify-content-end">

<div class="d-inline-flex align-items-baseline">

<strong class="mr-2">Total Amount:</strong>

<span id="totalAmount" style="padding-left: 15px;">@Model.Items.Sum(i => i.Quantity *


i.Price).ToString("C")</span>

</div>

</div>

</div>

<button type="submit" class="btn btn-primary">Submit Order</button>

</form>

@section Scripts {

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<script>

$(document).ready(function () {

$('.quantity').change(function () {

var total = 0;

$('tbody tr').each(function () {

var $row = $(this);

var qty = parseInt($row.find('.quantity').val(), 10);

var price = parseFloat($row.find('.unit-price').val()); // Get the unit price from the hidden
input

var totalPrice = qty * price;

// Update the displayed total price for each item


207

$row.find('.total-price').text('$' + totalPrice.toFixed(2));

// Add to overall total

total += totalPrice;

});

// Update the total amount displayed

$('#totalAmount').text('$' + total.toFixed(2));

});

});

</script>

}
Create another View named Success.cshtml in the Views/Order folder and then copy and
paste the following code.
@{

ViewData["Title"] = "Order Success";

<h2>Success</h2>

<p>Your order has been successfully processed!</p>


Testing the Application:
Now, run the application and navigate to the Order/Create URL, which will display the
following page. Please fill out the form, select the quantity of the Item, and then click on the
Submit Order button, as shown in the image below.
208

Once you click the Submit Order button, the order should be processed, and you should get
the following success message.

Points to Remember:
 Security: Using BindNever can be part of a strategy to prevent over-posting
attacks, in which users might attempt to set properties they should not be
able to set.
 Validation: BindRequired does not replace validation attributes like
[Required]. While BindRequired ensures the presence of a property in the
incoming data, [Required] validates that a property is not null after binding
has occurred.
 [BindNever] is used to protect specific properties from being affected by
model binding, usually for security reasons
 [BindRequired] ensures that certain fields are included in the request, which
is crucial for data completeness and validation.
Differences Between Required and BindRequired in ASP.NET
Core MVC
In ASP.NET Core MVC, the Required and BindRequired attributes are often confused
because they imply some form of “required-ness” for model properties. However, they serve
209

different purposes and are used in different contexts. Understanding the Differences
between them is important for correctly implementing data validation and model-binding
behavior in your applications.
Required Attribute
 Context: Data Annotation for Validation
 Purpose: The Required attribute is part of the
System.ComponentModel.DataAnnotations namespace and is used primarily
for model validation. It ensures that a property has a value; it is mainly used
to enforce that a field should not be null or empty during model validation.
 Behavior: When a model is validated (usually after model binding has
occurred), if a property marked with the Required attribute does not have a
value, the model state is considered invalid. This attribute affects the server-
side validation process and is also used to generate client-side validation
rules.
Example of Required Attribute:
using System.ComponentModel.DataAnnotations;

public class RegisterModel

[Required]

public string Email { get; set; } // Must have a non-null/non-empty value

}
BindRequired Attribute
 Context: Model Binding
 Purpose: The BindRequired attribute, from the
Microsoft.AspNetCore.Mvc.ModelBinding namespace handles model binding
behavior. It is used to indicate that a property must be present in the incoming
data for binding to succeed. If the property is not present in the incoming
request, the model state becomes invalid.
 Behavior: Unlike Required, BindRequired does not concern itself with
validating the property’s content (whether empty or null) but rather with its
presence in the request. This is useful for ensuring that key parts of the model
are not omitted in the request.
Example of BindRequired attribute usage:
using Microsoft.AspNetCore.Mvc.ModelBinding;

public class UpdateModel

[BindRequired]
210

public int Id { get; set; } // Must be present in the incoming request

}
Key Differences Between Required and BindRequired in ASP.NET Core
 Validation vs. Binding: Required validates that a property has a value after
binding has occurred, whereas BindRequired ensures that the property is
included in the incoming data before or during binding.
 Effect on ModelState: Both can make ModelState invalid for different
reasons: Required if the value is null or empty, and BindRequired if the key is
missing in the request data.
 Use Case: Use Required Attribute when you need to ensure that a property is
not empty or null (common for forms and data entry scenarios). Use
BindRequired when you need to ensure that certain data elements are
actually present in the incoming request, typically important in APIs and data
update scenarios.

the Zoom Credentials to attend the Free Demo Sessions


Back to: ASP.NET Core Tutorials For Beginners and Professionals
Real-Time Examples of Data Annotations in ASP.NET Core MVC
In this article, I will discuss Multiple Real-Time Examples of Data Annotations in
ASP.NET Core MVC Applications. Please read our previous article discussing BindNever
and BindRequired Attribute in ASP.NET Core MVC.
Real-Time Examples of Data Annotations in ASP.NET Core MVC
Data annotations play a crucial role in ASP.NET Core MVC applications, providing a way to
enforce validation rules and define configuration settings for models. Here are some real-
time, practical examples of how data annotations might be used:
User Registration Form:
When creating a user registration form, you want to ensure data integrity and provide
feedback to the user.
public class RegisterViewModel

[Required(ErrorMessage = "Email is required.")]

[EmailAddress(ErrorMessage = "Enter a valid email address.")]

[Display(Name = "Email Address")]

public string Email { get; set; }

[Required]
211

[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at most {1} characters
long.", MinimumLength = 6)]

[DataType(DataType.Password)]

[Display(Name = "Password")]

public string Password { get; set; }

[DataType(DataType.Password)]

[Display(Name = "Confirm Password")]

[Compare("Password", ErrorMessage = "Passwords do not match.")]

public string ConfirmPassword { get; set; }

}
Product Listing in an E-Commerce Application:
In an e-commerce application, you’d want to have constraints and proper display settings
for your product model when listing products.
Ad
1/2
00:46

Traveling across Quang Binh to stunning cinematic locations

public class Product

[Key]

public int ProductId { get; set; }

[Required]

[StringLength(255)]

[Display(Name = "Product Name")]

public string Name { get; set; }

[Required]
212

[Range(0.01, 99999.99, ErrorMessage = "Price must be between 0.01 and 99999.99.")]

[DataType(DataType.Currency)]

public decimal Price { get; set; }

[StringLength(2000)]

[Display(Name = "Product Description")]

public string Description { get; set; }

[DataType(DataType.Date)]

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

public DateTime ReleaseDate { get; set; }

}
Employee Management System:
For an application that manages employees, you’d likely want to enforce validation and
structure to your data.
public class Employee

[Key]

public int EmployeeId { get; set; }

[Required]

[StringLength(100)]

[Display(Name = "First Name")]

public string FirstName { get; set; }

[Required]

[StringLength(100)]

[Display(Name = "Last Name")]

public string LastName { get; set; }


213

[Required]

[Range(18, 65, ErrorMessage = "Age should be between 18 and 65.")]

public int Age { get; set; }

[Required]

[EmailAddress]

public string Email { get; set; }

[Phone]

public string PhoneNumber { get; set; }

[Display(Name = "Date of Joining")]

[DataType(DataType.Date)]

public DateTime JoinDate { get; set; }

}
Feedback Form:
You’d want to gather accurate and relevant information for a feedback or contact form on a
website.
public class FeedbackViewModel

[Required]

[StringLength(100)]

[Display(Name = "Your Name")]

public string Name { get; set; }

[Required]

[EmailAddress]

[Display(Name = "Your Email")]

public string Email { get; set; }


214

[Required]

[StringLength(1000, ErrorMessage = "Message should be no more than 1000 characters.")]

[DataType(DataType.MultilineText)]

public string Message { get; set; }

}
Hotel Booking System:
Data integrity for date ranges and room types is essential in a hotel booking application.
public class RoomBookingModel

[Key]

public int BookingId { get; set; }

[Required]

[Display(Name = "Guest Name")]

public string GuestName { get; set; }

[Required]

[DataType(DataType.Date)]

[Display(Name = "Check-In Date")]

public DateTime CheckInDate { get; set; }

[Required]

[DataType(DataType.Date)]

[Display(Name = "Check-Out Date")]

public DateTime CheckOutDate { get; set; }

[Required]

[EnumDataType(typeof(RoomType))]

[Display(Name = "Room Type")]


215

public RoomType TypeOfRoom { get; set; }

public enum RoomType

Single,

Double,

Suite

}
Library Management System:
Ensuring the right format for ISBN numbers and proper categorization of books is vital for a
library system.
public class BookModel

[Key]

public int BookId { get; set; }

[Required]

[Display(Name = "Book Title")]

public string Title { get; set; }

[Required]

[StringLength(13, MinimumLength = 10, ErrorMessage = "ISBN should be 10 or 13


characters long.")]

[Display(Name = "ISBN Number")]

public string ISBN { get; set; }

[Required]

[Display(Name = "Author")]
216

public string AuthorName { get; set; }

[DataType(DataType.Date)]

[Display(Name = "Publication Date")]

public DateTime PublishedDate { get; set; }

[EnumDataType(typeof(Genre))]

[Display(Name = "Book Genre")]

public Genre BookGenre { get; set; }

public enum Genre

Fiction,

NonFiction,

Biography,

Children,

Mystery,

Fantasy

}
Employee Portal:
Ensuring accurate data for roles, contact information, and employment dates is critical for a
company’s employee portal.
public class EmployeeModel

[Key]

public int EmployeeId { get; set; }

[Required]
217

[Display(Name = "First Name")]

public string FirstName { get; set; }

[Required]

[Display(Name = "Last Name")]

public string LastName { get; set; }

[Required]

[EmailAddress]

[Display(Name = "Email Address")]

public string Email { get; set; }

[Required]

[Phone]

[Display(Name = "Contact Number")]

public string PhoneNumber { get; set; }

[Required]

[EnumDataType(typeof(Role))]

[Display(Name = "Job Role")]

public Role EmployeeRole { get; set; }

[DataType(DataType.Date)]

[Display(Name = "Joining Date")]

public DateTime JoinDate { get; set; }

public enum Role

Admin,

Manager,
218

Developer,

Designer,

Sales

}
Online Shopping Portal:
When users want to register on an e-commerce site, data annotations can be used to
ensure valid and consistent data entry.
public class CustomerRegistrationViewModel

[Required]

[Display(Name = "Full Name")]

public string FullName { get; set; }

[Required]

[EmailAddress]

[Display(Name = "Email Address")]

public string Email { get; set; }

[Required]

[DataType(DataType.Password)]

[StringLength(12, MinimumLength = 5)]

public string Password { get; set; }

[DataType(DataType.Password)]

[Compare("Password", ErrorMessage = "The password and confirmation password do not


match.")]

public string ConfirmPassword { get; set; }


219

[Required]

[Phone]

public string PhoneNumber { get; set; }

[Required]

[Display(Name = "Shipping Address")]

public string Address { get; set; }

}
Health Clinic Appointment Booking:
When patients book an appointment, ensure the date and time are specified and gather
valid contact information.
public class AppointmentViewModel

[Required]

[DataType(DataType.Date)]

[Display(Name = "Appointment Date")]

public DateTime Date { get; set; }

[Required]

[DataType(DataType.Time)]

[Display(Name = "Preferred Time")]

public DateTime Time { get; set; }

[Required]

[Display(Name = "Reason for Visit")]

public string Reason { get; set; }

[Required]

[Phone]
220

[Display(Name = "Contact Number")]

public string ContactNumber { get; set; }

}
Blog Management System:
In a system where users can submit blog posts, data annotations can enforce character
limits, ensure necessary fields are filled, and handle other constraints.
public class BlogPostViewModel

[Required]

[StringLength(200)]

[Display(Name = "Blog Title")]

public string Title { get; set; }

[Required]

[StringLength(5000)]

public string Content { get; set; }

[DataType(DataType.Date)]

[Display(Name = "Publish Date")]

public DateTime PublishDate { get; set; }

[Display(Name = "Author's Email")]

[EmailAddress]

public string AuthorEmail { get; set; }

}
Job Application Portal:
Imagine a scenario where you’re building a job application portal, and applicants need to fill
out their details.
public class JobApplicationModel

{
221

[Required(ErrorMessage = "Please enter your first name.")]

[Display(Name = "First Name")]

public string FirstName { get; set; }

[Required(ErrorMessage = "Please enter your last name.")]

[Display(Name = "Last Name")]

public string LastName { get; set; }

[Required]

[EmailAddress(ErrorMessage = "Invalid Email Address.")]

public string Email { get; set; }

[Required]

[Phone]

[Display(Name = "Mobile Number")]

public string PhoneNumber { get; set; }

[Required]

[Display(Name = "Position Applying For")]

public string Position { get; set; }

[DataType(DataType.Upload)]

[Display(Name = "Upload Resume")]

public IFormFile Resume { get; set; }

}
Real Estate Listing:
For a real estate application, agents need to list properties with specific attributes:
public class PropertyListingModel

[Required]
222

[Display(Name = "Property Name")]

public string PropertyName { get; set; }

[Required]

[Range(1, 1000, ErrorMessage = "Enter a valid number of rooms.")]

public int Rooms { get; set; }

[Required]

[Display(Name = "Year Built")]

public int YearBuilt { get; set; }

[Required]

[DataType(DataType.Currency)]

[Range(1, 10000000, ErrorMessage = "Enter a valid price.")]

public decimal Price { get; set; }

[MaxLength(1000, ErrorMessage = "Description can't exceed 1000 characters.")]

public string Description { get; set; }

}
Booking a Workshop or Seminar:
For booking a workshop, attendees might need to provide some details:
public class WorkshopBookingModel

[Required]

[Display(Name = "Attendee Name")]

public string AttendeeName { get; set; }

[Required]

[EmailAddress]

public string Email { get; set; }


223

[Required]

[DataType(DataType.Date)]

[Display(Name = "Preferred Date")]

public DateTime PreferredDate { get; set; }

[Required]

[Display(Name = "Workshop Topic")]

public string Topic { get; set; }

}
Product Review on an E-Commerce Site:
Customers might want to leave reviews for products they’ve purchased:
public class ProductReviewModel

[Required]

public int ProductId { get; set; }

[Required]

[Range(1, 5)]

public int Rating { get; set; }

[Required]

[MaxLength(1000, ErrorMessage = "Review can't exceed 1000 characters.")]

public string Review { get; set; }

[Required]

[Display(Name = "Reviewed By")]

public string ReviewerName { get; set; }

}
Event Registration Form:
224

In an event registration form, attendees must provide details to secure a spot.


public class EventRegistrationModel

[Required]

[StringLength(100, ErrorMessage = "Name should not exceed 100 characters.")]

[Display(Name = "Attendee Name")]

public string Name { get; set; }

[Required]

[EmailAddress(ErrorMessage = "Invalid email format.")]

public string Email { get; set; }

[Phone(ErrorMessage = "Invalid phone number.")]

public string PhoneNumber { get; set; }

[Required]

[Display(Name = "Choose Event Session")]

public string Session { get; set; }

}
Order Placement in an E-commerce Platform:
When placing an order, users need to provide shipping details.
public class ShippingDetailsModel

[Required]

[Display(Name = "Recipient's Name")]

public string RecipientName { get; set; }

[Required]

[StringLength(200, ErrorMessage = "Address is too long.")]


225

public string Address { get; set; }

[Required]

[RegularExpression(@"^\d{5}$", ErrorMessage = "Invalid zip code.")]

public string ZipCode { get; set; }

[Required]

[Phone]

[Display(Name = "Contact Number")]

public string PhoneNumber { get; set; }

}
Gym Membership Form:
For a gym membership registration, data annotations ensure valid user data.
public class MembershipModel

[Required]

[Display(Name = "Full Name")]

public string FullName { get; set; }

[Required]

[DataType(DataType.Date)]

[Display(Name = "Date of Birth")]

public DateTime DateOfBirth { get; set; }

[Required]

[EmailAddress]

public string Email { get; set; }

[Required]

[Range(1, 12, ErrorMessage = "Select a valid membership duration.")]


226

[Display(Name = "Duration (in months)")]

public int Duration { get; set; }

}
Feedback for a Restaurant:
After dining in a restaurant, customers might want to leave feedback and rate their
experience.
public class RestaurantFeedbackModel

[Required]

[Display(Name = "Your Name")]

public string CustomerName { get; set; }

[Required]

[Range(1, 5, ErrorMessage = "Please select a rating between 1 and 5.")]

public int Rating { get; set; }

[StringLength(500, ErrorMessage = "Feedback should not exceed 500 characters.")]

public string Feedback { get; set; }

[EmailAddress(ErrorMessage = "Please provide a valid email address.")]

[Display(Name = "Email (optional)")]

public string Email { get; set; }

Fluent API Validation in ASP.NET Core MVC


In this article, I will briefly introduce Fluent API Validations in ASP.NET Core
MVC Applications. Please read our previous article discussing Data Annotations in
ASP.NET Core MVC Applications. When it comes to creating validation rules for models in
ASP.NET Core MVC (or any ASP.NET Core Web Application), we can adopt two different
227

approaches, i.e., Data Annotation and Fluent API Validations. We have already discussed
Model Validation using Data Annotation. In this article and in our few upcoming articles, we
are going to discuss Model Validations using Fluent API.
What is Fluent API Validation in ASP.NET Core MVC?
Fluent API validation in ASP.NET Core MVC is an approach to model validation that uses a
fluent interface provided by the FluentValidation library. This approach constructs validation
rules in a clear, flexible, understandable, and readable manner, which is an alternative to
the traditional Data Annotation approach.
So, instead of directly applying data annotation attributes to model properties, we can define
validation rules in a separate class with Fluent Validation. This “fluent” approach is based
on the Fluent Interface design pattern, which uses method chaining to create code that
reads like natural language.
Fluent API Validation Methods in ASP.NET Core
In ASP.NET Core, when we refer to Fluent API validation, we often talk about the library
FluentValidation. The FluentValidation provides a rich set of methods to define validation
rules fluently. Here are some of the common validation methods provided by
FluentValidation:
Traveling across Quang Binh to stunning cinematic locations
Basic Validators:
 NotNull() / NotEmpty(): Check the property is not null or empty.
 Equal(): Checks if the property equals another value or property.
 NotEqual(): Checks the property is not equal to another value or property.
 Length(): Checks the length of a string property that falls within the given min
and max range.
 Matches(): Checks the property matches a given regex pattern.
Number Validators:
 InclusiveBetween(): Checks the property value falls between or equals to
the specified limits. This can also be used for DateTime ranges.
 ExclusiveBetween(): Checks the property value falls between but not equal
to the specified limits. This can also be used for DateTime values.
 LessThan() / LessThanOrEqualTo(): For comparison with another value.
This can also be used for DateTime values.
 GreaterThan() / GreaterThanOrEqualTo(): Same as above, but for greater
values. This can also be used for DateTime values.
String Validators:
 EmailAddress(): Checks the property is a valid email address.
 CreditCard(): Checks the property is a valid credit card number.
Conditionally Applying Rules:
 When(): Apply rules conditionally based on properties or external factors.
 Unless(): The opposite of When(), the rule is applied unless the condition is
true.
Collections:
 Must(): This allows for more complex custom logic. For instance, you could
validate that a list has unique elements. It can also be used to write custom
logic for validation.
 ForEach(): Apply a rule to each element in a collection.
Custom Validators:
228

 Custom(): Allows you to specify a custom validation function.


Other Validators:
 CascadeMode: Determines how the library should continue validation once a
rule has failed.
 WithMessage(): Allows custom error messages to be set for validation rules.
 WithName(): Overrides the display name of a property.
 DependentRules(): Specifies that several rules should be grouped together
as dependent rules.
Async Validators:
 MustAsync(): Asynchronously checks a condition.
 CustomAsync(): Asynchronously validates using custom logic.
Comparison Validators:
 Equal() / NotEqual(): This can also be used to compare properties of the
same model.
How Do We Use Fluent API Validation in ASP.NET Core MVC?
To Fluent Validation in ASP.NET Core MVC, we need to add the FluentValidation package,
which is a popular choice for using Fluent API validation. Let us proceed and see the step-
by-step process of integrating and using FluentValidation in an ASP.NET Core MVC
Application. So, first, create a new ASP.NET Core MVC Project with Model View Controller
Project Template and name the project FluentAPIDemo.
Install FluentValidation Package:
First, we need to add the FluentValidation.AspNetCore package to our project. We can
add using NuGet Package Manager for the Solution, as shown below.

Using Package Manager Console: Install-Package FluentValidation.AspNetCore


Creating the Model Class:
We will use Fluent API Validations to validate the model class property values. Let us first
create a model class for registering a user. Create a class file
named RegistrationModel.cs within the Models folder and copy and paste the following
code. This simple model class has three properties: Username, Email, and Password, and
we want to validate these properties.
namespace FluentAPIDemo.Models
229

public class RegistrationModel

public string? Username { get; set; }

public string? Email { get; set; }

public string? Password { get; set; }

}
Creating a Fluent API Validator:
A validator class in Fluent Validation is where you define the rules for each property of the
model you want to validate. To implement Fluent API Validation to Validate the Model
Properties, we need to create a Fluent API Validator class, and for each model class, we
need to create a separate Validator class. For example, for our Registration Model, we need
to create a Validator Class, and as part of the Validator class, we need to write the logic to
validate the properties, as per our business requirement.
So, create a class file named RegistrationValidator.cs within the Models folder and copy
and paste the following code. Here, we use the built-in Fluent API validation methods to
validate the RegistrationModel properties.
using FluentValidation;

namespace FluentAPIDemo.Models

public class RegistrationValidator : AbstractValidator<RegistrationModel>

public RegistrationValidator()

RuleFor(x => x.Username)

.NotEmpty().WithMessage("Username is Required.")

.Length(5, 30).WithMessage("Username must be between 5 and 30 characters.");

RuleFor(x => x.Email)


230

.NotEmpty().WithMessage("Email is Required.")

.EmailAddress().WithMessage("Valid Email Address is Required.");

RuleFor(x => x.Password)

.NotEmpty().WithMessage("Password is Required.")

.Length(6, 100).WithMessage("Password must be between 6 and 100 characters.");

}
Understanding the above Code:
Namespace and Usings: The code uses the FluentValidation library. The using
FluentValidation; statement allows access to the FluentValidation classes and methods.
Class Definition: The RegistrationValidator class is defined, which inherits
from AbstractValidator<T> generic class. This generic base class is provided by Fluent
Validation, where T represents the type of object to validate, in this
case, RegistrationModel. So, it is the base class that we need to extend to define our
validation rules for a specific model.
Validator Constructor: The constructor public RegistrationValidator() sets up rules for the
properties of RegistrationModel. The Validation Rules are as follows:
Username Validation:
 RuleFor(x => x.Username): Defines a validation rule for the Username
property of RegistrationModel.
 .NotEmpty(): Specifies that the Username must not be empty. If it is empty,
the message “Username is Required.” will be shown.
 .Length(5, 30): Ensures that the length of the Username must be between 5
and 30 characters. If not, the message “Username must be between 5 and 30
characters.” is displayed.
Email Validation:
 RuleFor(x => x.Email): Sets a rule for the Email property.
 .NotEmpty(): The Email cannot be empty, and if it is, “Email is Required.” will
be the error message.
 .EmailAddress(): Validates that the input is a valid email address format. If it
fails, “Valid Email Address is Required.” is shown as the error message.
Password Validation:
 RuleFor(x => x.Password): Defines a rule for the Password property.
 .NotEmpty(): Checks that the Password is not empty, displaying “Password
is Required.” if it is.
 .Length(6, 100): Ensures the Password length is between 6 and 100
characters. If the length criteria are not met, “Password must be between 6
and 100 characters.” will be shown.
Points to Remember:
231

 RuleFor(x => x.PropertyName): This is the starting method to define a


validation rule for a specific property on the model. It sets the context for
further chained methods determining how the property will be validated.
 WithMessage(“…”): This method allows us to provide a custom error
message for the preceding validation rule. If the validation fails, this message
will be added to the ModelState errors in an ASP.NET Core MVC application.
Registering Fluent API and the Validator:
Once we have the Model class and Validator class, we need to enable Fluent API validation
for our Project. To enable Fluent API Validation, we need to use
the AddFluentValidationAutoValidation service, as shown below, within the Program
class of our application. This will enable integration between FluentValidation and ASP.NET
MVC’s automatic validation pipeline. That means the ASP.NET Core framework will check
the validation automatically.
builder.Services.AddFluentValidationAutoValidation();
If you want to enable Client-Side validation, then you need to register the
following AddFluentValidationClientsideAdapters method. This is not required if you
work with an ASP.NET Core Web API Project. This enables integration between
FluentValidation and ASP.NET client-side validation.
builder.Services.AddFluentValidationClientsideAdapters();
Then, we must register each Model class and the corresponding Validator class to the built-
in dependency injection container. Here, we are using the AddTransient method to register
the same as follows.
builder.Services.AddTransient<IValidator<RegistrationModel>,
RegistrationValidator>();
With the above changes in place, our Program class should look as shown below:
using FluentAPIDemo.Models;

using FluentValidation;

using FluentValidation.AspNetCore;

using System.Drawing;

namespace FluentAPIDemo

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);


232

// Add services to the container.

builder.Services.AddControllersWithViews();

//Enables integration between FluentValidation and ASP.NET MVC's automatic validation


pipeline.

builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on client side

builder.Services.AddTransient<IValidator<RegistrationModel>, RegistrationValidator>();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

app.UseExceptionHandler("/Home/Error");

// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.

app.UseHsts();

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(

name: "default",
233

pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

}
The FluentValidation will automatically validate controller action method parameters,
populating the ModelState with validation errors. You can then check ModelState.IsValid in
your action methods to determine if the received input is valid.
Validating in Controllers:
Now, we need to Validate the RegistrationModel within the Controller action method. You
need to remember that FluentValidation will automatically validate the model and populate
the ModelState. So, modify the Home Controller class as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()

return View();

[HttpPost]

public IActionResult Register(RegistrationModel model)

if (!ModelState.IsValid)

{
234

// Validation failed, return to the form with errors

return View(model);

// Handle successful validation logic here

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Display Errors in Views:
We can display errors using the validation tag helpers in our Razor views. So, add
the Register.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel

@{

ViewData["Title"] = "Register";

<h1>Register User</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register">

<div asp-validation-summary="All" class="text-danger"></div>


235

<div class="form-group mt-3"">

<label asp-for="Username" class="control-label"></label>

<input asp-for="Username" class="form-control" />

<span asp-validation-for="Username" class="text-danger"></span>

</div>

<div class="form-group mt-3"">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group mt-3"">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" class="form-control" />

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group mt-3"">

<input type="submit" value="Create" class="btn btn-primary" />

</div>

</form>

</div>

</div>
Now, run the application, navigate to Home/Register, and check whether everything works
as expected.
236

Here, you can observe that the Client-Side validation is not working. To ensure the client-
side validation is working, we must include jQuery, jQuery Validation, and jQuery
Unobtrusive Validation in our project. These can be added via CDNs or local files. For
example, include them via CDNs in your _Layout.cshtml:
<!-- jQuery -->

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>

<!-- jQuery Validation Plugin -->

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.2/
jquery.validate.min.js"></script>

<!-- jQuery Unobtrusive Validation (to bridge ASP.NET Core MVC validation with jQuery
Validate) -->

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/
jquery.validate.unobtrusive.min.js"></script>
237

To enable client-side validation using local files, you can add the following files to
your _Layout.cshtml. The order is also important; please include them in the following
order.
<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>

<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></
script>
However, as we already discussed, the above files are already added within
the _ValidationScriptsPartial partial view. In the _Layout file, one optional section is
provided named Scripts. So, we can include a section called Scripts and call the
_ValidationScriptsPartial partial view inside that section. Please modify
the Register.cshtml view as follows. We can add the Script section and call the
_ValidationScriptsPartial Partial View wherever the client-side validation is required.
@model FluentAPIDemo.Models.RegistrationModel

@{

ViewData["Title"] = "Register";

<h1>Register User</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register">

<div asp-validation-summary="ModelOnly" class="text-danger"></div>

<div class="form-group mt-3">

<label asp-for="Username" class="control-label"></label>

<input asp-for="Username" class="form-control" />

<span asp-validation-for="Username" class="text-danger"></span>

</div>

<div class="form-group mt-3">


238

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" class="form-control" />

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Create" class="btn btn-primary" />

</div>

</form>

</div>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
With the above changes, you can observe that the client-side validation is also working as
expected.
Validating Manually:
Instead of using the ModelState.IsValid to check the Validation automatically, we can also
check the validation manually by creating an instance of the Validator class and then
invoking the Validate method. For a better understanding, please look at the following code.
using FluentAPIDemo.Models;
239

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()

return View();

[HttpPost]

public IActionResult Register(RegistrationModel model)

RegistrationValidator validator = new RegistrationValidator();

var validationResult = validator.Validate(model);

if (!validationResult.IsValid)

return View(model);

//if (!ModelState.IsValid)

//{

// // Validation failed, return to the form with errors

// return View(model);

//}

// Handle successful validation logic here


240

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
With the above changes, run the application and see the expected behavior. Note that not
all rules defined in FluentValidation will work with ASP.NET’s client-side validation. For
example, rules defined using a condition (with When/Unless), Custom Validators, or Calls to
Must will not run on the client side. The client-side validation supports the following
Validator Methods in ASP.NET Core:
 NotNull/NotEmpty
 Matches (regex)
 InclusiveBetween (range)
 CreditCard
 Email
 EqualTo (cross-property equality comparison)
 MaxLength
 MinLength
 Length
What happens when using Fluent Validation and Data Annotations in
ASP.NET Core MVC with the Same Model Properties?
When Fluent Validation and Data Annotations are used in an ASP.NET Core MVC
application to validate the same model properties, both sets of validation rules are applied.
 Data Annotations: ASP.NET Core Processes Data Annotations validation
automatically when model binding occurs. This means that when the
framework attempts to bind incoming data to a model, it checks the Data
Annotations rules.
 Fluent Validation: If you have configured Fluent Validation to integrate with
ASP.NET Core’s automatic validation pipeline, it generally runs after Data
Annotations during the model binding process. If you invoke Fluent Validation
manually (e.g., inside a controller action), you control when it runs relative to
Data Annotations.
Example:
Let’s understand this with an example. To include the Data Annotation Validations, let’s
modify the RegistrationModel.cs class file as follows. You can see we have decorated the
Model Properties with Data Annotation Attributes.
241

using System.ComponentModel.DataAnnotations;

namespace FluentAPIDemo.Models

public class RegistrationModel

[Required(ErrorMessage = "Please Enter Username")]

public string? Username { get; set; }

[Required(ErrorMessage = "Please Enter Email")]

[EmailAddress(ErrorMessage = "Please Enter Valid Email Address")]

public string? Email { get; set; }

[Required(ErrorMessage = "Please Enter Password")]

public string? Password { get; set; }

}
Next, modify the Home Controller as follows. The ASP.NET Core framework will
automatically handle Data annotation and Fluent validations at the time of Model Binding as
we have enabled the Auto Validations for Fluent API.
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()

{
242

return View();

[HttpPost]

public IActionResult Register(RegistrationModel model)

if (!ModelState.IsValid)

// Validation failed, return to the form with errors

return View(model);

// Handle successful validation logic here

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
To see the effect, please modify the Register.cshtml file as follows. Here, we are disabling
the client-side validation by removing the script section that invokes the Partial View.
@model FluentAPIDemo.Models.RegistrationModel

@{

ViewData["Title"] = "Register";

}
243

<h1>Register User</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group mt-3">

<label asp-for="Username" class="control-label"></label>

<input asp-for="Username" class="form-control" />

<span asp-validation-for="Username" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" class="form-control" />

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Create" class="btn btn-primary" />

</div>
244

</form>

</div>

</div>

@* @section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

} *@
Now, run the application and click on the Create button without providing any data, and you
will get the following error. In each individual span element where the model-specific error is
displayed, you will not see both error messages as they always overwrite the previous error
message with the latest one. However, if you observe the validation summary, it shows all
the error messages, as shown in the image below, which proves that both Validations are
working.
245

Issues When Use Both:


 Redundancy: Duplication of validation rules can lead to confusion and
increased maintenance overhead.
 Performance: Running both validations may slightly impact performance,
although this is generally minimal unless the rules are complex or the model
is large.
Best Practices:
 Avoid Duplication: Ensure that validations are not redundantly defined in
both systems.
 Clear Separation: Use Data Annotations for simple, declarative validations
and Fluent Validation for complex logic.
 Documentation: Clearly document which validations are applied where,
especially if both systems are used extensively across the application.
Why Do We Need to Use Fluent API Validation in ASP.NET Core?
Fluent API validation offers several advantages over traditional validation methods in
ASP.NET Core, such as Data Annotations. Here are some reasons why developers choose
Fluent API validation over Data Annotation:
 Readability and Clarity: Fluent API uses method chaining to express
configuration and setup in an easy-to-read and understandable way. This can
246

make the code more clear and self-documenting. For example, a validation
rule might look like this: .RuleFor(model =>
model.Property).NotEmpty().WithMessage(“Property cannot be empty”);
 Centralized Validation Rules: This allows you to define all validation rules
for a model in one place. This centralization helps avoid duplication of
validation logic across different parts of an application, such as controllers,
views, services, etc., making the code cleaner and reducing maintenance
overhead. This also makes the rules easier to manage and modify.
 Customization and Flexibility: Fluent API validators are highly
customizable, allowing developers to define complex validation rules that are
not easily implemented using data annotations. You can also create and use
custom validation methods in the validation chain.
 Customizable Error Messages: Fluent API allows users to customize error
messages easily and dynamically based on validation contexts. This flexibility
makes error responses more meaningful for the end user.
 Reusability: Since validation rules are defined in separate classes (usually
one per model), you can reuse these validators across different application
parts, ensuring consistent validation logic throughout.
 Integration with ASP.NET Core Features: These validators work
seamlessly with ASP.NET Core MVC features like model binding, automatic
HTTP 400 responses on validation failures, and integration with ModelState.
This means you can easily tie your custom validation logic into the standard
ASP.NET Core pipeline and handle errors effectively.
 Support for Asynchronous Validation: Fluent API supports asynchronous
validation rules, which are essential for non-blocking I/O operations, such as
database or network calls, improving the performance of your web
application.
 Interdependent Validations: Fluent API can handle complex scenarios
where the validity of one field may depend on the state or value of another
field within the model. This interdependency is more awkward to implement
with traditional attribute-based validation.
Performance Differences Between Data Annotation and Fluent API
Validation in ASP.NET Core MVC:
Data Annotation: Data Annotations may lead to slightly faster performance because they
are less complex and integrated directly into the model properties. However, the difference
is often negligible in most applications.
Fluent API Validation: In runtime performance, there might be a negligible performance
overhead with Fluent Validation because it involves compiling and executing more complex
validation rules. However, this difference is generally small and can be offset by the benefits
of more powerful validation capabilities.

Fluent API Validation Examples in ASP.NET Core MVC


In this article, I will discuss Different Types of Fluent API Validation Examples in
ASP.NET Core MVC Applications. Please read our previous article discussing the basic
concepts of Fluent API Validations in ASP.NET Core MVC. Please read our previous
article, in which we discussed installing the Fluent API Package and configuring it to use
247

Fluent Validation on both the Client and Server Sides. At the end of this article, you will
understand examples using the following Validators.
1. Fluent API Basic Validators Examples
2. Fluent API Number Validators Examples
3. String Validators in ASP.NET Core MVC
4. Fluent API Date and Time Validators
5. Conditional Validation using Fluent API in ASP.NET Core MVC
6. Fluent API Collections Validator
7. Fluent API Other Validators in ASP.NET Core MVC
Basic Fluent API Validators Example in ASP.NET Core MVC:
FluentValidation in ASP.NET Core MVC offers many built-in validators. The following are
some of the most common basic Fluent API validators.
 NotNull() / NotEmpty(): Check the property is not null or empty.
 Equal(): Checks if the property equals another value or property.
 NotEqual(): Checks the property is not equal to another value or property.
 Length(): Checks the length of a string property that falls within the given min
and max range.
 Matches(): Checks the property matches a given regex pattern.
Let us see one real-time example to understand Fluent API NotNull(), NotEmpty(), Equal(),
NotEqual(), Length(), and Matches() methods in ASP.NET Core MVC Application.
Define the Model:
Let’s assume we have the Registration model.

NextStayTraveling across Quang Binh to stunning cinematic locations00:43 / 03:1710


Sec

namespace FluentAPIDemo.Models

public class RegistrationModel

public string? Username { get; set; }

public string? Password { get; set; }

public string? ConfirmPassword { get; set; }

public string? Email { get; set; }

}
248

Set Up the Validator:


Now, let’s define a validator for this model.
using FluentValidation;

namespace FluentAPIDemo.Models

public class RegistrationValidator : AbstractValidator<RegistrationModel>

public RegistrationValidator()

RuleFor(r => r.Username)

.NotNull().WithMessage("Username is Required")

.NotEmpty().WithMessage("Username cannot be Empty")

.Length(5, 30).WithMessage("Username must be between 5 and 30 characters");

RuleFor(r => r.Password)

.NotNull().WithMessage("Password is Required")

.NotEmpty().WithMessage("Password cannot be Empty")

.Length(6, 50).WithMessage("Password must be between 6 and 50 Characters.")

.Matches("(?!^[0-9]*$)(?!^[a-zA-Z]*$)^([a-zA-Z0-9]{2,})$").WithMessage("Password can
only contain alphanumeric characters");

RuleFor(r => r.ConfirmPassword)

.Equal(r => r.Password).WithMessage("Confirm Password must match Password");

RuleFor(r => r.Email)

.NotEmpty().WithMessage("Email is required")

.EmailAddress().WithMessage("Invalid Email Format")

.NotEqual("admin@example.com").WithMessage("This Email is Not Allowed");


249

}
Registering the Validator:
Add the following code to the Program class to register Fluent API Validation, the Model,
and the corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.

builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on client-side

builder.Services.AddTransient<IValidator<RegistrationModel>, RegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()

return View();

[HttpPost]
250

public IActionResult Register(RegistrationModel model)

if (!ModelState.IsValid)

// Validation failed, return to the form with errors

return View(model);

// Handle successful validation logic here

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel

@{

ViewData["Title"] = "Register";

<h1>Register User</h1>

<hr />
251

<div class="row">

<div class="col-md-4">

<form asp-action="Register">

<div class="form-group mt-3">

<label asp-for="Username" class="control-label"></label>

<input asp-for="Username" class="form-control" />

<span asp-validation-for="Username" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" class="form-control" />

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="ConfirmPassword" class="control-label"></label>

<input asp-for="ConfirmPassword" class="form-control" />

<span asp-validation-for="ConfirmPassword" class="text-danger"></span>

</div>

<div class="form-group mt-3">


252

<input type="submit" value="Create" class="btn btn-primary" />

</div>

</form>

</div>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
With the above changes, run the application, and you should get the validation error
message as expected, as shown in the image below.
253

Fluent API Number Validators Example in ASP.NET Core MVC:


The Fluent API provides the following Number Validators:
 InclusiveBetween(): Checks the property value falls between or equals to
the specified limits.
 ExclusiveBetween(): Checks the property value falls between but not equal
to the specified limits.
 LessThan() / LessThanOrEqualTo(): For comparison with another value.
 GreaterThan() / GreaterThanOrEqualTo(): Same as above, but for greater
values.
Let’s look at an example to understand the above Fluent API Number Validator methods in
the ASP.NET Core MVC application.
Define the Model:
Let’s assume we have the following Product model with properties like Price, Discount, and
StockCount.
namespace FluentAPIDemo.Models

public class Product


254

public decimal Price { get; set; }

public decimal Discount { get; set; } // Let's assume this is a percentage value.

public int StockCount { get; set; }

}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;

namespace FluentAPIDemo.Models

public class ProductValidator : AbstractValidator<Product>

public ProductValidator()

// Validation rule for Price

RuleFor(p => p.Price)

.GreaterThan(0).WithMessage("Price must be greater than 0.")

.LessThan(10000).WithMessage("Price must be less than 10000.");

// Validation rule for Discount

RuleFor(p => p.Discount)

.InclusiveBetween(0, 100).WithMessage("Discount must be between 0% and 100%


inclusive.");

// Validation rule for StockCount

RuleFor(p => p.StockCount)


255

.GreaterThanOrEqualTo(0).WithMessage("Stock count shoud be between 1 and 500")

.LessThanOrEqualTo(500).WithMessage("Stock count shoud be between 1 and 500");

}
In this Validator:
 GreaterThan, LessThan, GreaterThanOrEqualTo, and LessThanOrEqualTo
set specific bounds on numeric values.
 InclusiveBetween ensures the value lies within (and including) the provided
range.
 ExclusiveBetween would ensure the value lies within the range but excluding
the boundaries (this method is not used in the above example, but you could
replace InclusiveBetween with it for a different behavior).
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<Product>, ProductValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult AddProduct()

return View();

}
256

[HttpPost]

public IActionResult AddProduct(Product product)

if (!ModelState.IsValid)

// Validation failed, return to the form with errors

return View(product);

// Handle successful validation logic here

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add AddProduct.cshtml view and
copy and paste the following code.
@{

ViewData["Title"] = "Add Product";

<h1>Add Product</h1>

<hr />
257

<div class="row">

<div class="col-md-4">

<form asp-action="AddProduct">

<div class="form-group mt-3">

<label asp-for="Price" class="control-label"></label>

<input asp-for="Price" class="form-control" />

<span asp-validation-for="Price" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Discount" class="control-label"></label>

<input asp-for="Discount" class="form-control" />

<span asp-validation-for="Discount" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="StockCount" class="control-label"></label>

<input asp-for="StockCount" class="form-control" />

<span asp-validation-for="StockCount" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Create" class="btn btn-primary" />

</div>

</form>

</div>

</div>
258

@* @section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

} *@
Now, run the application, and you should see the proper validation messages in the image
below.

String Fluent API Validators in ASP.NET Core MVC:


FluentValidation offers a range of string-specific validation methods used in ASP.NET Core
MVC applications. They are as follows:
 EmailAddress(): Ensures that the string is a valid email address format.
 CreditCard(): Validates the string as a credit card number.
 PrecisionScale: Defines a scale precision validator on the current rule
builder that ensures a decimal of the specified precision and scale.
 Matches: Ensures the property matches a specified regex pattern.
Let us see one example of understanding the string Fluent API Validation methods using a
PaymentInfo model.
Define the Model:
259

Let’s assume we have the following PaymentInfo model, which has properties like
CardHolderName, CardNumber, BillingEmail, and Amount. We will use this model to
capture a user’s payment details.
namespace FluentAPIDemo.Models

public class PaymentInfo

public string? CardHolderName { get; set; }

public string? CardNumber { get; set; }

public string? BillingEmail { get; set; }

public decimal Amount { get; set; }

}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;

namespace FluentAPIDemo.Models

public class PaymentInfoValidator : AbstractValidator<PaymentInfo>

public PaymentInfoValidator()

//EmailAddress: Ensures that the string is a valid email address format

RuleFor(p => p.BillingEmail)

.EmailAddress().WithMessage("Please enter a valid email address.");

//CreditCard: Validates the string as a credit card number.


260

RuleFor(p => p.CardNumber)

.CreditCard()

.WithMessage("Please enter a valid credit card number.");

//PrecisionScale: Checks the number of allowable decimal places and the overall precision of
a number.

RuleFor(p => p.Amount)

.PrecisionScale(8, 2, true)

.WithMessage("Amount can have up to 2 decimal places and up to 8 total digits.");

//Matches: Ensures the property matches a specified regex pattern.

//We want the CardHolderName to only contain letters and spaces:

RuleFor(p => p.CardHolderName).Matches(@"^[a-zA-Z\s]+$").WithMessage("Name can


only contain letters and spaces.");

}
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<PaymentInfo>, PaymentInfoValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller


261

public IActionResult ProcessPayment()

return View();

[HttpPost]

public IActionResult ProcessPayment(PaymentInfo paymentInfo)

if (!ModelState.IsValid)

return View(paymentInfo); // Return with validation errors.

// Process payment logic...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add ProcessPayment.cshtml view
and copy and paste the following code.
@model FluentAPIDemo.Models.PaymentInfo
262

@{

ViewData["Title"] = "Process Payment";

<h1>Process Payment</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="ProcessPayment">

@* <div asp-validation-summary="All" class="text-danger"></div> *@

<div class="form-group mt-3">

<label asp-for="CardNumber" class="control-label"></label>

<input asp-for="CardNumber" class="form-control" />

<span asp-validation-for="CardNumber" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="CardHolderName" class="control-label"></label>

<input asp-for="CardHolderName" class="form-control" />

<span asp-validation-for="CardHolderName" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Amount" class="control-label"></label>

<input asp-for="Amount" class="form-control" />

<span asp-validation-for="Amount" class="text-danger"></span>

</div>
263

<div class="form-group mt-3">

<label asp-for="BillingEmail" class="control-label"></label>

<input asp-for="BillingEmail" class="form-control" />

<span asp-validation-for="BillingEmail" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Process Payment" class="btn btn-primary" />

</div>

</form>

</div>

</div>

@* @section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

} *@
Now, run the application, and you should see the proper validation messages as expected,
as shown in the below image.
Fluent API DateTime Validators in ASP.NET Core MVC:
Let’s understand Fluent API DateTime Validations in the ASP.NET Core MVC application
context. We can use the following validator methods to validate the date and time.
 Must: For more complex scenarios. For instance, to ensure the date is not a
weekend
 InclusiveBetween(): This can also be used for DateTime ranges.
 LessThan() / LessThanOrEqualTo(): This can also be used for DateTime
values.
 GreaterThan() / GreaterThanOrEqualTo(): Same as above, but for future
DateTime values.
Define the Model:
Let’s say we have an event scheduling application. Our model Event will have EventDate,
as shown below.
264

namespace FluentAPIDemo.Models

public class Event

public DateTime EventDate { get; set; }

}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;

namespace FluentAPIDemo.Models

public class EventValidator : AbstractValidator<Event>

public EventValidator()

// Ensure the event date is in the future

RuleFor(e => e.EventDate)

.GreaterThan(DateTime.Now).WithMessage("Event Date must be in the future");

// For demonstration, let's assume we want events only within the next 30 days

RuleFor(e => e.EventDate)

.LessThan(DateTime.Now.AddDays(30)).WithMessage("Event Date must be within the next


30 days");

// Ensure the event date is not on a weekend

RuleFor(e => e.EventDate)


265

.Must(date => date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek !=


DayOfWeek.Sunday)

.WithMessage("Events on weekends are not allowed");

// InclusiveBetween example, ensuring date is within a specific range

//RuleFor(e => e.EventDate)

// .InclusiveBetween(DateTime.Now, DateTime.Now.AddMonths(1))

// .WithMessage("Event Date must be within the next month");

}
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<Event>, EventValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult ScheduleEvent()

return View();
266

[HttpPost]

public IActionResult ScheduleEvent(Event eventModel)

if (!ModelState.IsValid)

return View(eventModel); // Return with validation errors.

// Proceed with the event scheduling...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add ScheduleEvent.cshtml view
and copy and paste the following code.
@model FluentAPIDemo.Models.Event

@{

ViewData["Title"] = "ScheduleEvent";

<h1>Schedule Event</h1>
267

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="ScheduleEvent">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group">

<label asp-for="EventDate" class="control-label"></label>

<input asp-for="EventDate" type="date" class="form-control" />

<span asp-validation-for="EventDate" class="text-danger"></span>

</div>

<div class="form-group">

<input type="submit" value="Schedule Event" class="btn btn-primary" />

</div>

</form>

</div>

</div>
Now, run the application, and you should see the proper validation messages.
Conditional Validation using Fluent API in ASP.NET Core MVC:
Conditional Validation is one of FluentValidation’s powerful features. It allows us to perform
validation rules based on certain conditions. This can be achieved using
the When() and Unless() methods. The When and Unless methods in FluentValidation offer
conditional validation. They allow you to specify under which conditions a particular
validation rule should be executed.
 When(): Apply rules conditionally based on properties or external factors.
 Unless(): The opposite of When(), the rule is applied unless the condition is
true.
Imagine an e-commerce application where users can register and, while creating an
account, provide valid phone numbers to receive promotional offers.
Define the Model:
Here’s the Registration model:
namespace FluentAPIDemo.Models
268

public class Registration

public bool WantsPromotions { get; set; }

public string? PhoneNumber { get; set; }

}
Set Up the Validator:
Now, let’s define a validator for this model. We want the Phone Number only mandatory if
the WantsPromotions is “true.”
using FluentValidation;

namespace FluentAPIDemo.Models

public class RegistrationValidator : AbstractValidator<Registration>

public RegistrationValidator()

RuleFor(r => r.PhoneNumber)

.NotEmpty().WithMessage("Phone number is required if you want promotions.")

.When(r => r.WantsPromotions); // Conditionally applies the rule if WantsPromotions is true

RuleFor(r => r.PhoneNumber)

.Must(p => p.StartsWith("+") && p.Length > 10)

.WithMessage("Phone number should start with '+' and be longer than 10 digits.")

.Unless(r => string.IsNullOrEmpty(r.PhoneNumber)); // Applies the rule unless the


condition is true (i.e., phone number is empty or null)
269

}
In this example:
 The NotEmpty() validation for PhoneNumber only applies when
WantsPromotions is true.
 The Must() validation for PhoneNumber applies unless the phone number is
null or empty.
Registering the Validator:
Add the following code in the Program class to register the Model and corresponding
validator.
//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<Registration>, RegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()

return View();

[HttpPost]

public IActionResult Register(Registration registration)

if (!ModelState.IsValid)
270

return View(registration); // Return with validation errors.

// Proceed with registration...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.Registration

@{

ViewData["Title"] = "Register";

<h1>Register</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register">

<div asp-validation-summary="All" class="text-danger"></div>


271

<div class="form-group">

<label asp-for="WantsPromotions">I want promotions</label>

<input asp-for="WantsPromotions" type="checkbox" />

</div>

<div class="form-group">

<label asp-for="PhoneNumber" class="control-label"></label>

<input asp-for="PhoneNumber" class="form-control" />

<span asp-validation-for="PhoneNumber" class="text-danger"></span>

</div>

<div class="form-group">

<input type="submit" value="Register" class="btn btn-primary" />

</div>

</form>

</div>

</div>
Now, run the application, and you should see the proper validation messages.
Fluent API Collections Validator in ASP.NET Core MVC:
Fluent API Provides Must and ForEach methods to validate collections and their individual
items in ASP.NET Core MVC. The Must method in FluentValidation offers custom validation
logic, while ForEach can be employed to validate individual items within a collection
 Must(): Allows for more complex custom logic.
 ForEach(): Apply a rule to each element in a collection.
Let’s say we have a Cart model representing a shopping cart containing a list of Product
items:
Define the Model:
Let’s define a model for Book and Order:
namespace FluentAPIDemo.Models

public class Product


272

public string? Name { get; set; }

public int Quantity { get; set; }

public class Cart

public List<Product> Products { get; set; } = new List<Product>();

}
Set Up the Validators:
Product Validator:
Firstly, define a validator for individual Product items:
using FluentValidation;

namespace FluentAPIDemo.Models

public class ProductValidator : AbstractValidator<Product>

public ProductValidator()

RuleFor(p => p.Name).NotEmpty().WithMessage("Product name is required.");

RuleFor(p => p.Quantity)

.GreaterThan(0)

.WithMessage("Product quantity must be greater than 0.");

}
273

}
Cart Validator:
Now, define a validator for the Cart, applying the ProductValidator to each item in the
Products collection and ensuring the cart contains at least one product:
using FluentValidation;

namespace FluentAPIDemo.Models

public class CartValidator : AbstractValidator<Cart>

public CartValidator()

RuleFor(c => c.Products)

.NotEmpty()

.WithMessage("Cart must contain at least one product.");

RuleForEach(c => c.Products).SetValidator(new ProductValidator());

RuleFor(c => c.Products)

.Must(ContainUniqueProducts)

.WithMessage("Products in the cart must be unique.");

private bool ContainUniqueProducts(List<Product> products)

var uniqueProductNames = new HashSet<string>();

foreach (var product in products)

if (!uniqueProductNames.Add(product.Name))
274

return false;

return true;

}
In this CartValidator, we have a Must validator that ensures all products in the cart are
unique, along with ForEach applying a ProductValidator to each product in the cart.
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<Cart>, CartValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Checkout()

Cart cart = new Cart();

List<Product> products = new List<Product>()

new Product(){Name = "Product1", Quantity=1},


275

new Product(){Name = "Product2", Quantity=2},

new Product(){Name = "Product2", Quantity=5},

new Product(){Name = "Product3", Quantity=0},

};

cart.Products = products;

return View(cart);

[HttpPost]

public IActionResult Checkout(Cart cart)

if (!ModelState.IsValid)

return View(cart); // Return with validation errors.

// Continue with order placement...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
276

You can use the tag helpers to bind and display errors. Add Checkout.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.Cart

@{

ViewData["Title"] = "Checkout";

<h1>Checkout</h1>

<form asp-action="Checkout" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

@for (int i = 0; i < Model.Products.Count; i++)

<!-- Product Name -->

<label asp-for="@Model.Products[i].Name">Product Name</label>

<input asp-for="@Model.Products[i].Name" />

<span asp-validation-for="@Model.Products[i].Name" class="text-danger"></span>

<!-- Quantity -->

<label asp-for="@Model.Products[i].Quantity">Quantity</label>

<input asp-for="@Model.Products[i].Quantity" type="number" />

<span asp-validation-for="@Model.Products[i].Quantity" class="text-danger"></span>

<br/>

<input type="submit" value="CheckOut" class="btn btn-primary" />

</form>
Now, run the application, and you should see the proper validation messages.
Fluent API Other Validators in ASP.NET Core MVC:
Let us understand the following Fluent API Validator Methods.
277

 CascadeMode: CascadeMode Determines how the validator should continue


its validation process once a validation rule has failed.
 WithMessage(): WithMessage allows customizing the error message for a
specific validation rule.
 WithName(): WithName is used to specify a custom property name within
error messages. This is especially useful if you want your error messages to
display a friendlier or more descriptive property name than the actual
property.
 DependentRules(): Specifies that several rules should be grouped together
as dependent rules. That means DependentRules allows you to define
additional rules that will only be executed if the main rule passes.
Define the Model:
Consider the following UserRegistration model.
namespace FluentAPIDemo.Models

public class UserRegistration

public string Email { get; set; }

public string Password { get; set; }

public string ConfirmPassword { get; set; }

}
Set Up the Validator:
Now, let’s define a validator for this model:
using FluentValidation;

namespace FluentAPIDemo.Models

public class UserRegistrationValidator : AbstractValidator<UserRegistration>

public UserRegistrationValidator()

{
278

//CascadeMode Determines how the validator should continue its validation process

//once a validation rule has failed.

//CascadeMode.Stop: Stop on the first failure

//CascadeMode.Continue: Execution continues to the next rule/validator.

//In the following validator, if Email is null,

//then the EmailAddress validation won't even be checked because of the CascadeMode.Stop.

ClassLevelCascadeMode = CascadeMode.Stop;

//WithMessage(): Allows customizing the error message for a specific validation rule.

RuleFor(x => x.Password)

.NotNull()

.MinimumLength(6).WithMessage("Password must be at least 6 characters long.");

//WithName(): Overrides the display name of a property.

//When a validation error occurs on the Email property,

//the error message will refer to the property as "User Email Address".

RuleFor(x => x.Email)

.NotNull()

.EmailAddress()

.WithName("User Email Address");

//In the following rule, the validator checks if ConfirmPassword is equal to Password.

//If it is, the dependent rule for Password being not empty is checked.

RuleFor(x => x.ConfirmPassword)

.Equal(x => x.Password).WithMessage("Passwords must match.")

.DependentRules(() =>

{
279

RuleFor(x => x.Password).NotEmpty().WithMessage("Password cannot be empty when


Confirm Password is provided.");

});

}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<UserRegistration>, UserRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()

return View();

[HttpPost]

public IActionResult Register(UserRegistration registration)

{
280

if (!ModelState.IsValid)

return View(registration); // Return with validation errors.

// Continue with the registration process...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.UserRegistration

@{

ViewData["Title"] = "Register";

<h1>User Register</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register" asp-controller="Home" method="post">


281

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" class="form-control" />

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="ConfirmPassword" class="control-label"></label>

<input asp-for="ConfirmPassword" class="form-control" />

<span asp-validation-for="ConfirmPassword" class="text-danger"></span>

</div>

<div class="form-group">

<input type="submit" value="Register" class="btn btn-primary" />

</div>

</form>

</div>

</div>
282

Fluent API Async Validators in ASP.NET Core MVC:


In this article, I will discuss Fluent API Async Validator in ASP.NET Core
MVC Applications with Examples. Please read our previous article discussing the Different
Types of Fluent API Validation Examples in ASP.NET Core MVC Applications.
Fluent API Async Validators in ASP.NET Core MVC:
Fluent API Async Validators in ASP.NET Core MVC allows us to perform complex validation
logic that integrates with the model binding and validation processes asynchronously, which
can be useful for operations that require I/O operations like database lookups, API calls, or
other time-consuming processes that should not block the main execution flow. The
following two Validator Methods are provided by Fluent API to perform the validation
asynchronously.
 MustAsync(): MustAsync() is used when applying a custom asynchronous
predicate function to a property. This method is particularly useful when the
validation logic involves asynchronous operations such as database checks,
API calls, or other IO-bound operations.
 CustomAsync(): Use CustomAsync() when you need more control over the
validation process than what MustAsync() offers. This method allows you to
perform any asynchronous operation using custom logic.
Let’s understand how to use these Fluent API Asynchronous Validators in an ASP.NET
Core MVC application.
Define the Model:
Let us consider a simple UserRegistration model where we want to ensure that the provided
email address isn’t already registered. So, create a class file
named UserRegistration.cs and copy and paste the following code.
Ad
1/2
00:47

Traveling across Quang Binh to stunning cinematic locations

namespace FluentAPIDemo.Models

public class UserRegistration

public string? Email { get; set; }


283

public string? Password { get; set; }

}
Service
We need to create a service to Check Whether the Email ID Exists in the Database. So,
create a class file named UserService.cs and copy and paste the following code. As you
can see in the following code, the EmailExistsAsync method is used to check if an email is
registered.
namespace FluentAPIDemo.Models

public class UserService

public Task<bool> EmailExistsAsync(string email)

//In Real-Time, you need to check the database

//here, we are hard coding the Email Ids

List<string> emails = new List<string>()

"user1@example.com", "user2@example.com", "user2@example.com",


"user4@example.com"

};

if (emails.Contains(email))

return Task.FromResult(true);

else
284

return Task.FromResult(false);

}
Registering the UserService
Please add the following code to the Program.cs class file.
builder.Services.AddTransient<UserService>();
Set Up the Validator:
Now, let’s define a validator for the UserRegistration model. So, create a class file
named UserRegistrationValidator.cs and copy and paste the following code. As you can
see in the following code, we have used the MustAsync and CustomAsync validator
methods to write the custom logic, invoking the EmailExistsAsync method to validate
whether the Email is already registered.
using FluentValidation;

namespace FluentAPIDemo.Models

public class UserRegistrationValidator : AbstractValidator<UserRegistration>

public UserRegistrationValidator(UserService userService)

// Synchronous validation

RuleFor(x => x.Password)

.NotEmpty()

.WithMessage("Password is required.");

// Asynchronous validation

// MustAsync
285

//RuleFor(x => x.Email)

// .NotEmpty()

// .EmailAddress()

// .MustAsync(async (email, cancellation) =>

// !await userService.EmailExistsAsync(email))

// .WithMessage("Email is already registered.");

// Asynchronous validation

// CustomAsync

RuleFor(p => p.Email)

.CustomAsync(async (email, context, cancellation) =>

bool isUnique = await userService.EmailExistsAsync(email);

if (isUnique)

context.AddFailure($"Email: {email} is already registered");

});

}
Explanation:
 MustAsync: This method is also used for asynchronous operations. The
provided predicate should return true if validation succeeds and false if it fails.
In this case, it invokes
the userService.EmailExistsAsync(email) asynchronous method (this
method must be asynchronous) and should return true or false.
286

 CustomAsync: The CustomAsync Validator method provides greater


flexibility than MustAsync, allowing for custom failure messages based on
complex asynchronous logic. In this case, it is also calling
the userService.EmailExistsAsync(email) async method, and then, based
on the outcome, it also sets a custom error message, which is impossible
using the MustAsync method.
Registering the Validator:
Add the following code to the Program class to register Fluent API Validation, the Model,
and the corresponding model validator. You must disable auto-validation when working with
the async validator; otherwise, you will get a run-time exception.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.

//builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on client side

builder.Services.AddTransient<IValidator<UserRegistration>, UserRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows. Since we’re using asynchronous validation, ensure
that the ASP.NET Core MVC Controller action method is also asynchronous. With the
async validator in place, Auto Validation will not work, so we need to create an instance
of UserRegistrationValidator and invoke the ValidateAsync method by passing
the UserRegistration object as a parameter that needs to be validated.
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

private readonly UserService _userService;

public HomeController(UserService userService)

{
287

_userService = userService;

public IActionResult Register()

return View();

[HttpPost]

public async Task<IActionResult> Register(UserRegistration registration)

var validationResult = await new


UserRegistrationValidator(_userService).ValidateAsync(registration);

if (!validationResult.IsValid)

foreach (var error in validationResult.Errors)

ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

return View(registration); // Return with validation errors.

// Redirect to the Success Page

return RedirectToAction("Success");

public string Success()

{
288

return "Registration Successful";

}
Displaying Errors in Views:
Now, we need to display the validator error inside a view. We can use the tag helpers to
bind and display model validation errors here. So, add Register.cshtml view and copy and
paste the following code.
@model FluentAPIDemo.Models.UserRegistration

@{

ViewData["Title"] = "Register";

<h1>User Register</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group mt-3">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" type="password" class="form-control" />


289

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Register" class="btn btn-primary" />

</div>

</form>

</div>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
Now, run the application, and you should see the Validation Messages as expected, as
shown in the below image:
290

The most important point is to use the MustAsync and CustomAsync Fluent API Validators
only when necessary. Performing too many I/O-bound operations during validation can slow
down your application. Secondly, with MustAsync and CustomAsync Fluent API Validators,
ASP.NET Core MVC Auto Validation will not work, and in that case, we need to do the
validation manually by calling the ValidateAsync method.
Differences Between Fluent API Sync vs. Async Validators in
ASP.NET Core MVC
Fluent API provides options for both synchronous and asynchronous validation. Here are
the key differences between using Fluent API’s synchronous and asynchronous validators
in ASP.NET Core:
Fluent API Synchronous Validators
Synchronous validators are the traditional type of validators that execute on the server in a
blocking manner as part of the request processing pipeline. This means the thread handling
the request will wait until the validation is complete before continuing.
 Execution: Runs on the current thread and blocks it until the validation is
complete.
 Usage Scenario: This is ideal for quick validations that do not involve I/O
operations, like checking string lengths, numeric ranges, or complex rules
that don’t require database or network calls.
 API Methods: Typically, methods like Validate() are used.
Fluent API Asynchronous Validators
Asynchronous validators, on the other hand, allow the validation process to be non-
blocking. They are designed to perform I/O-bound tasks, such as database lookups or calls
to external services, without blocking the executing thread.
 Execution: Runs without blocking the thread. It allows the thread to be used
for other tasks while waiting for the validation logic that involves I/O
operations to complete.
 Usage Scenario: Best suited for validations that involve external data
sources, like database constraint checks, API calls, or any operations that
can be delayed without impacting the thread’s ability to serve other requests.
 API Methods: Methods like ValidateAsync() are used.
Choosing Between Sync and Async Validators
The choice between synchronous and asynchronous validators typically depends on the
nature of the validation task:
 Performance Considerations: Synchronous validators are generally
sufficient and simpler to implement for non-I/O-bound validations.
Asynchronous validators should be considered when the validation involves
I/O operations, which could potentially slow down the request processing if
done synchronously.
 Resource Utilization: Asynchronous validators help better utilize server
resources, especially under load, as they free up the server thread to handle
other requests while waiting for I/O operations to complete.
291

Fluent API Custom Validators in ASP.NET Core MVC


In this article, I will discuss How to Implement Fluent API Custom Validators in ASP.NET
Core MVC Applications with Examples. Please read our previous article discussing Fluent
API Async Validator in ASP.NET Core MVC Applications.
Fluent API Custom Validators in ASP.NET Core MVC:
When working with Fluent API Validation, custom validators can be useful when the built-in
validators are insufficient for our business needs. We can create Custom Validators for both
Synchronous and Asynchronous Validation. We can use the Custom or Must method
for Synchronous Validation or the CustomAsync or MustAsync method
for Asynchronous Validation.
How Do We Implement a Custom Validator with Fluent API?
To implement a Custom Validator using Fluent API in ASP.NET Core MVC, we need to
follow the below steps:
 Create the Custom Validator Class: This class will contain the validation
logic. It should implement IValidator<T> where T is the type of the model
you’re validating.
 Define Validation Rules: Within your custom validator class, define the rules
by using Custom or CustomAsync methods provided by Fluent Validation. For
example, RuleFor(x => x.Property).Custom((value, context) => { /*
custom validation logic here */ });
 Register the Validator: In the Program.cs, add your custom validator to the
service collection so it can be discovered and used by ASP.NET Core. For
example, builder.Services.AddValidatorsFromAssemblyContaining<MyC
ustomValidator>();
 Apply the Validator: Ensure your controller actions are set up to check the
ModelState or explicitly invoke the validation process by creating an instance
of the Validator and invoking the Validate or ValidateAsync method by passing
the model object.
Let’s understand how to create custom validations using the Fluent API Custom, Must,
CustomAsync, and MustAsync Validator methods in an ASP.NET Core MVC Application.
Ad
1/2
00:02

Traveling across Quang Binh to stunning cinematic locations

Example: User Registration


In ASP.NET Core MVC, using Custom Synchronous and Asynchronous Validation methods
allows you to add more dynamic checks that might involve database access or other
external services. Let us understand this with one real-time scenario where we might want
to validate a user’s registration data against an existing database to ensure that email
addresses and usernames are unique.
Assume you have a user registration form where users need to provide a username and an
email. Both the username and the email must be unique in your system.
Define the Model:
292

First, define the registration form model. We will use the following RegistrationModel to
understand Fluent API Custom Validators in an ASP.NET Core MVC Application. Create a
class file named RegistrationModel.cs within the Models folder and copy and paste the
following code. As you can see, this model class contains three properties.
namespace FluentAPIDemo.Models

public class RegistrationModel

public string Username { get; set; }

public string Email { get; set; }

public string Password { get; set; }

}
Create Custom Validator
You need to create a custom validator class to access the database and check for
Username and Email uniqueness. In the following class, we have hard-coded the data
source, but you must check it against the database table in real-time. So, create a class file
named UserValidator.cs within the Models folder and copy and paste the following code.
namespace FluentAPIDemo.Models

public class UserValidator

private readonly List<RegistrationModel> users = new List<RegistrationModel>()

new RegistrationModel(){Username= "User1", Email="User1@Example.com", Password =


"User1Password"},

new RegistrationModel(){Username= "User2", Email="User2@Example.com", Password =


"User2Password"},
293

new RegistrationModel(){Username= "User3", Email="User3@Example.com", Password =


"User3Password"},

new RegistrationModel(){Username= "User4", Email="User4@Example.com", Password =


"User4Password"},

};

public bool BeUniqueUsername(string username)

// Check if any user in the database has the same username

return users.Any(u => u.Username.Equals(username,


System.StringComparison.OrdinalIgnoreCase));

public async Task<bool> BeUniqueEmailAsync(string email)

// Check if any user in the database has the same email

var result = users.Any(u => u.Email.Equals(email,


System.StringComparison.OrdinalIgnoreCase));

return await Task.FromResult(result);

}
Explanation
 BeUniqueUsername Method: This method uses LINQ’s Any() to determine if
any user in the list has the same username as the one provided. It returns
false if such a user exists, else false. The
StringComparison.OrdinalIgnoreCase makes the comparison case-
insensitive.
 BeUniqueEmailAsync Method: Similarly, this method checks for the
uniqueness of the email. It is marked with async; to fulfill the method’s
signature as async, we return Task.FromResult(…).
294

Registering UserValidator:
Add the following code to the Program class to register the UserValidator with the built-in
dependency injection container.
builder.Services.AddTransient<UserValidator>();
Next, using FluentValidation, we need to apply these custom methods to validate the
registration form.
Creating Custom Validator Using Custom and CustomAsync Method in
ASP.NET Core:
Let us see how to implement the Custom Validation Logic using
the Custom and CustomAsync Validator method. So, create a class file
named RegistrationModelValidator.cs and copy and paste the following code. As you can
see, this class is inherited from the AbstractValidator<RegistrationModel> class. As you
can see, the UserValidator is injected into the RegistrationModelValidator, and using this
instance, we are calling the BeUniqueUsername and BeUniqueEmailAsync method using
the Custom and CustomAsync Validator method. The following code is self-explained, so
please go through the comment lines.
using FluentValidation;

namespace FluentAPIDemo.Models

public class RegistrationModelValidator : AbstractValidator<RegistrationModel>

public RegistrationModelValidator(UserValidator userValidator)

RuleFor(x => x.Username)

//Specifies that the username cannot be empty

.NotEmpty().WithMessage("Username is required.")

//Adds a custom validation rule where the uniqueness of the username is checked.

//This is a synchronous custom validator:

.Custom((userName, context) =>

//userValidator.BeUniqueUsername(userName) is called to check if the username is unique

bool isExists = userValidator.BeUniqueUsername(userName);


295

//If isExists is true (meaning the userName is exists in the database)

if (isExists)

// adds a failure message to the validation context,

// indicating that the username is already registered

context.AddFailure($"{userName} is already registered");

});

RuleFor(p => p.Email)

//Ensures the email field is not empty, with a corresponding error message if the check fails

.NotEmpty().WithMessage("Email is required.")

//Validates that the input is in a proper email format, with a custom message if the format is
incorrect.

.EmailAddress().WithMessage("Email is not a valid email address.")

//An asynchronous custom validator for email uniqueness:

.CustomAsync(async (email, context, cancellation) =>

//It uses userValidator.BeUniqueEmailAsync(email) to check asynchronously if the email is


unique.

bool isExists = await userValidator.BeUniqueEmailAsync(email);

//If isExists is true (meaning the email is exists in the database)

if (isExists)

// adds a failure message to the validation context,


296

// indicating that the email is already registered

context.AddFailure($"{email} is already registered");

});

}
Registering the Validator:
Add the following code to the Program class to register Fluent API Validation, the Model,
and the corresponding validator. As we are using Asynchronous Validation, we should
disable Auto validation.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.

//builder.Services.AddFluentValidationAutoValidation();

//Enables Integration Between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on client side

builder.Services.AddTransient<IValidator<RegistrationModel>,
RegistrationModelValidator>();
Use in Controller:
Finally, use the RegistrationModelValidator in your controller to handle user registration.
Modify the Home Controller as follows. Now that we have disabled Auto Validation, we need
to create an instance of lValidator and call the ValidateAsync method by passing
the RegistrationModel object to check whether the model validation is successful, as
shown in the code below.
using FluentAPIDemo.Models;

using FluentValidation;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers
297

public class HomeController : Controller

private readonly IValidator<RegistrationModel> _validator;

public HomeController(IValidator<RegistrationModel> validator)

_validator = validator;

public IActionResult Register()

return View();

[HttpPost]

public async Task<IActionResult> Register(RegistrationModel model)

var validationResult = await _validator.ValidateAsync(model);

if (!validationResult.IsValid)

foreach (var error in validationResult.Errors)

ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

return View(model);

}
298

// Continue with adding the product...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
We must bind the model properties in the Razor View and display validation messages
using ASP.NET Core’s built-in tag helpers. Add a view named Register.cshtml within the
Views/Home folder and copy and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel

@{

ViewData["Title"] = "Register";

<h1>User Register</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group mt-3">

<label asp-for="Username" class="control-label"></label>

<input asp-for="Username" class="form-control" />


299

<span asp-validation-for="Username" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="Password" class="control-label"></label>

<input asp-for="Password" type="password" class="form-control" />

<span asp-validation-for="Password" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Register" class="btn btn-primary" />

</div>

</form>

</div>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
300

With the above changes in place, run the application, provide the existing User Name and
Email, and click on the Create button to see if everything is working as expected, as shown
in the image below.

So, by using the Custom and CustomAsync methods, we can create validation rules
according to our business requirements, which are not provided by the built-in Fluent API
Validator.
Custom Validation using Must and MustAsync Fluent API Methods in
ASP.NET Core MVC
You can also write custom validation logic in an ASP.NET Core MVC Application using
Must and MustAsync Methods. These methods enable custom validation logic for specific
properties synchronously or asynchronously, respectively. These methods are useful when
the built-in Fluent API validators do not meet our business needs.
Let us understand the Use of Must and MustAsync with a Real-time Example. Let us create
an application for Venue Reservations. We will validate the Booking model for our venue
reservation system. The validation will ensure the booking date and time are valid, check for
venue availability, and use dependency injection to access external services.
Define the Model:
Let’s consider the following Booking model to understand Custom Validation using Must
and MustAsync Fluent API Methods. So, add a class file named Booking.cs within the
Models folder and then copy and paste the following code.
namespace FluentAPIDemo.Models
301

public class Booking

public int VenueId { get; set; }

public DateTime StartDate { get; set; }

public DateTime EndDate { get; set; }

public string CustomerEmail { get; set; }

}
Booking Service Validator:
You need to create a custom service class to access the database, check if a venue is
available on a given date, and verify if a customer is registered and active. For simplicity, I
am providing basic in-memory implementations in the following class. So, create a class file
named BookingService.cs within the Models folder and copy and paste the following code.
using System;

using System.Collections.Generic;

using System.Linq;

namespace FluentAPIDemo.Models

public class BookingService

private readonly List<BookedVenue> _bookedVenues = new List<BookedVenue>()

new BookedVenue(1, DateTime.Today.AddDays(1)), // Venue 1 booked tomorrow

new BookedVenue(2, DateTime.Today.AddDays(2)) // Venue 2 booked in two days

};
302

private readonly Dictionary<string, bool> _customers = new Dictionary<string, bool>

{ "User1@Example.com", true },

{ "User2@Example.com", false } // Inactive customer

};

public bool IsVenueAvailable(int venueId, DateTime date)

// Check if the venue is booked on the given date

return !_bookedVenues.Any(b => b.VenueId == venueId && b.Date.Date == date.Date);

public bool IsCustomerActive(string email)

// Check if the customer email exists and is active

return _customers.TryGetValue(email, out bool isActive) && isActive;

public class BookedVenue

public int VenueId { get; }

public DateTime Date { get; }

public BookedVenue(int venueId, DateTime date)

VenueId = venueId;

Date = date;
303

}
The above BookingService class uses data encapsulated within lists and dictionaries to
manage and check states related to bookings and customer activity. The IsVenueAvailable
method ensures that venues cannot be double-booked for the same date, while the
IsCustomerActive method allows the system to verify the active status of a customer before
allowing them to make a booking.
Registering UserValidator:
Add the following code to the Program class to register the UserValidator with the built-in
dependency injection container.
builder.Services.AddTransient<BookingService>();
Next, using FluentValidation, we need to apply these custom methods to validate the
registration form.
Creating Custom Validator with Must and MustAsync Methods:
Let us see how to implement the Custom Validation Logic using the Must and MustAsync
Validator method. So, create a class file named BookingValidator.cs and copy and paste
the following code. As you can see, this class is inherited from the
AbstractValidator<Booking> class. As you can see, the BookingService is injected into the
BookingValidator, and using this instance, we are calling the IsVenueAvailable and
IsCustomerActive method using the Custom and CustomAsync Validator method. The
following code is self-explained, so please go through the comment lines.
using FluentValidation;

namespace FluentAPIDemo.Models

public class BookingValidator : AbstractValidator<Booking>

public BookingValidator(BookingService bookingService)

// Validate StartDate and EndDate

RuleFor(booking => booking.StartDate)

.Must(date => date > DateTime.Now)

.WithMessage("The start date must be in the future.");


304

RuleFor(booking => booking.EndDate)

.GreaterThanOrEqualTo(booking => booking.StartDate)

.WithMessage("The end date must be after the start date.");

//Synchronous Checking Venue Availability

RuleFor(booking => booking)

.Must(booking => bookingService.IsVenueAvailable(booking.VenueId, booking.StartDate))

.WithMessage("The selected venue is not available on the start date.");

//Asynchronous Checking to Validate Customer Status

RuleFor(p => p.CustomerEmail)

//Ensures the email field is not empty, with a corresponding error message if the check fails

.NotEmpty().WithMessage("Email is required.")

//Validates that the input is in a proper email format, with a custom message if the format is
incorrect.

.EmailAddress().WithMessage("Email is not a valid email address.")

//Asynchronous Checking Customer Status

.MustAsync(async (email, cancellationToken) =>

await bookingService.IsCustomerActive(email))

.WithMessage("The customer must be active.");

}
Explanation
 Must Method: The Must method is used for simple, CPU-bound checks like
date comparisons, which don’t require IO operations.
 MustAsync Method: The MustAsync method is ideally used for IO-bound
operations such as database calls or API requests.
Registering the Validator:
305

Add the following code to the Program class to register the Model and corresponding
validator.
builder.Services.AddTransient<IValidator<Booking>, BookingValidator>();
Using Booking Validator in ASP.NET Core MVC Actions:
Let’s create an ASP.NET Core MVC controller that uses the Booking model and the
BookingValidator. Modify the Home Controller as follows. Now that we have disabled Auto
Validation, we need to create an instance of lValidator and call the ValidateAsync method
by passing the BookingModel object to check whether the model validation is successful,
as shown in the code below.
using FluentAPIDemo.Models;

using FluentValidation;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

private readonly IValidator<Booking> _validator;

public HomeController(IValidator<Booking> validator)

_validator = validator;

public IActionResult Register()

return View();

[HttpPost]

public async Task<IActionResult> Register(Booking model)

{
306

var validationResult = await _validator.ValidateAsync(model);

if (!validationResult.IsValid)

foreach (var error in validationResult.Errors)

ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

return View(model);

// Continue with adding the product...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Modify the Register.cshtml within
the Views/Home folder as follows. The following view uses tag helpers to bind model
properties to form fields, automatically handles validation messages, and uses a partial view
to include client-side validation scripts.
@model FluentAPIDemo.Models.Booking

@{

ViewData["Title"] = "Booking";
307

<h1>User Register</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group mt-3">

<label asp-for="VenueId" class="control-label"></label>

<input asp-for="VenueId" class="form-control" />

<span asp-validation-for="VenueId" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="StartDate" class="control-label"></label>

<input asp-for="StartDate" class="form-control" />

<span asp-validation-for="StartDate" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="EndDate" class="control-label"></label>

<input asp-for="EndDate" class="form-control" />

<span asp-validation-for="EndDate" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<label asp-for="CustomerEmail" class="control-label"></label>


308

<input asp-for="CustomerEmail" class="form-control" />

<span asp-validation-for="CustomerEmail" class="text-danger"></span>

</div>

<div class="form-group mt-3">

<input type="submit" value="Booking" class="btn btn-primary" />

</div>

</form>

</div>

</div>

@section Scripts {

@{

await Html.RenderPartialAsync("_ValidationScriptsPartial");

}
With the above changes in place, run the application, provide an inactive User Email,
provide the wrong Event Date, and click the Booking button to see if everything is working
as expected, as shown in the image below.
309

When Should We Use Fluent API Custom Validators in ASP.NET Core


MVC?
In ASP.NET Core MVC, using Fluent API custom validators is useful when you need to
implement complex validation rules that are not easily handled by the built-in Data
Annotations or built-in Fluent API Validator methods. Here are some of the scenarios where
you might choose to use Fluent API Custom Validators ASP.NET Core Application including
MVC, Web API:
 Complex Business Rules: If your validation logic involves multiple
properties of the model or complex business rules that depend on external
data, Fluent API Custom Validators allow you to encapsulate this logic neatly.
For example, you might want to validate that a booking date is not only in the
future but also on a day when a particular venue is available.
310

 Reusable Validation Logic: When you have validation rules that are used
across different models or parts of your application, Fluent API can help you
centralize and reuse this logic without duplicating code. For instance, if
several models require a tax ID number to be validated in a specific format,
you can create a custom validator for this purpose.
 Integration with ASP.NET Core’s Validation Pipeline: Custom Validators
created with Fluent API can easily integrate with ASP.NET Core’s model
validation system. This means they work well with the existing framework
features like model binding, producing validation results that automatically
translate into HTTP responses.
 Custom Validation Responses: If you need to return detailed validation
responses or customize the format of the validation error messages, Fluent
API allows more flexibility compared to Data Annotations. This can be
particularly useful for APIs where you might want to provide more detailed
error information to the client.
When Should We Use Fluent API Custom, CustomAsync, Must, and
MustAsync Methods in ASP.NET Core MVC?
When deciding between using Custom, CustomAsync, Must, and MustAsync methods in
Fluent Validation for ASP.NET Core MVC Application, the choice largely depends on the
complexity of the validation requirement, whether the validation involves asynchronous
operations, and the level of control needed over the validation process.
Must:
This is a synchronous method that takes a predicate function. The function takes the
property value as a parameter and returns a boolean indicating whether the property meets
a specific condition.
 Simple Condition: The validation rule is straightforward and can be
expressed as a simple condition or predicate.
 Synchronous Operation: No asynchronous operation is needed (e.g., no
database or external service checks).
 Example Scenarios: Validating that a number falls within a specific range;
checking if a string meets certain criteria (e.g., format or contains specific
characters).
MustAsync:
This is the asynchronous version of Must. It is useful when the validation rule needs to
perform asynchronous operations, such as database lookups or API calls, to determine
validity.
 Asynchronous Condition: The validation involves asynchronous operations
such as database checks or calls to external APIs.
 Non-blocking Requirement: It’s important to keep the operation non-
blocking, especially in web applications, to maintain responsiveness.
 Example Scenarios: Checking if a username is available by querying a
database asynchronously. Verifying if a record exists or meets certain criteria
in an external API.
Custom:
This method is synchronous and allows for custom validation logic. You can define a
function that takes the property value and a validation context and then returns a
ValidationResult. This method is useful when the validation depends on multiple properties
of the model.
311

 Complex Validation Logic: The validation depends on multiple properties or


requires complex logic that doesn’t neatly fit into a single predicate.
 Detailed Error Reporting: You need to add multiple failure messages or
detailed custom responses based on various conditions within the same rule.
 Example Scenarios: Ensuring a date range is valid where the start date
must be earlier than the end date, and both dates might depend on other
conditions in the model. Validating a model where the validation outcome
depends on a combination of fields (e.g., if Field A is true, then Field B must
have a specific value).
CustomAsync:
This is the asynchronous version of Custom. It is similar in usage but allows for including
async operations in the validation process.
 Complex Asynchronous Validation: Similar to Custom, but where the
validation logic includes asynchronous operations.
 Handling of Asynchronous Dependencies: The validation involves
dependencies that are inherently asynchronous, such as real-time data
fetching or validation against a service that requires waiting for a response.
 Example scenarios: Verify a file path by asynchronously checking whether
the file exists on a remote server. Conducting a multi-step validation where
steps involve fetching data from a database or a web service that must not
block the main execution thread.
Choosing the Right Method
 Simplicity vs. Complexity: Use Must/MustAsync for straightforward
conditions and Custom/CustomAsync for more complex scenarios involving
multiple conditions or complex logic.
 Synchronous vs. Asynchronous Needs: Choose between synchronous
and asynchronous methods based on whether the validation logic must
perform IO operations or other asynchronous tasks.
 Single vs. Multiple Validation Points: If your validation rule needs to
generate different messages based on different conditions, Custom or
CustomAsync provides the necessary control.
 Validation Context Usage: The Custom and CustomAsync methods provide
access to the entire validation context, allowing for more detailed inspection
and manipulation of the validation outcomes, which is particularly useful when
the validation decision involves multiple fields or complex logic.

Real-Time Examples of Fluent API Validations in ASP.NET Core MVC


In this article, I will discuss Different Real-Time Examples of Fluent API Validations in
ASP.NET Core MVC Applications. Please read our previous article discussing Fluent API
Custom Validator in ASP.NET Core MVC Applications. FluentValidation is particularly
powerful when handling complex validation scenarios. Here, we will explore some complex
validation cases and how they can be implemented using FluentValidation in ASP.NET
Core MVC.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Order Processing
312

Let us understand How we can validate the order processing in an e-commerce application
using Fluent API Validation.
Define the Model:
Let’s consider the following Order model.
namespace FluentAPIDemo.Models

public class Order

public string OrderId { get; set; }

public DateTime OrderDate { get; set; }

public decimal Amount { get; set; }

public string? CouponCode { get; set; }

public List<OrderItem>? OrderItems { get; set; }

public class OrderItem

public string? ProductName { get; set; }

public int Quantity { get; set; }

public decimal UnitPrice { get; set; }

}
Set Up the Validator:
Following are the Validation Rules that we need to check while processing the order:

Traveling across Quang Binh to stunning cinematic locations00:04 / 03:1710 Sec


 Ensure OrderId matches a certain pattern.
 Ensure that when a CouponCode is provided, the Amount exceeds a certain
value.
313

 Validate a collection, ensuring each OrderItem has valid data and additional
custom rules.
Implementing Complex Validators:
using FluentValidation;

namespace FluentAPIDemo.Models

public class OrderValidator : AbstractValidator<Order>

public OrderValidator()

// Ensure OrderId matches a pattern (e.g., ORD-XXXX where XXXXXX is numbers)

RuleFor(o => o.OrderId)

.Matches("^ORD-\\d{6}$")

.WithMessage("OrderId must follow the pattern ORD-XXXXXX.");

// Ensure that when a CouponCode is provided, the Amount exceeds a certain value (e.g.,
$100)

When(o => !string.IsNullOrEmpty(o.CouponCode), () =>

RuleFor(o => o.Amount)

.GreaterThan(100)

.WithMessage("Amount must exceed $100 to use a coupon.");

});

// Validate each OrderItem in the OrderItems collection

RuleForEach(o => o.OrderItems)

.SetValidator(new OrderItemValidator());
314

public class OrderItemValidator : AbstractValidator<OrderItem>

public OrderItemValidator()

RuleFor(i => i.ProductName)

.NotEmpty()

.WithMessage("ProductName cannot be empty.");

RuleFor(i => i.Quantity)

.GreaterThan(0)

.WithMessage("Quantity must be greater than 0.");

// Ensure that if UnitPrice is 0, Quantity is not more than 3 (e.g., max 3 free items per order)

RuleFor(i => i.UnitPrice)

.GreaterThan(0)

.When(i => i.Quantity > 3)

.WithMessage("Maximum of 3 free items are allowed.");

}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.
315

builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<Order>, OrderValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult CreateOrder()

List<OrderItem> products = new List<OrderItem>()

new OrderItem(){ProductName = "Product1", Quantity=1, UnitPrice=10},

new OrderItem(){ProductName = "Product2", Quantity=2, UnitPrice=15},

new OrderItem(){ProductName = "Product2", Quantity=2, UnitPrice=0},

new OrderItem(){ProductName = "Product3", Quantity=1, UnitPrice=25},

};

decimal TotalAmount = 0;

foreach (var item in products)

{
316

TotalAmount = TotalAmount + ((decimal)(item.Quantity * item.UnitPrice));

Order order = new Order()

OrderDate = DateTime.Now,

OrderId = "ORD-123456",

Amount = TotalAmount,

OrderItems = products

};

return View(order);

[HttpPost]

public IActionResult CreateOrder(Order order)

if (!ModelState.IsValid)

return View(order); // Return with validation errors.

// Process the order...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";


317

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add CreateOrder.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.Order

@{

ViewData["Title"] = "CreateOrder";

<h1>CreateOrder</h1>

<div class="row">

<div class="col-md-4">

<form asp-action="CreateOrder" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group">

<label asp-for="OrderId" class="control-label"></label>

<input asp-for="OrderId" class="form-control" />

<span asp-validation-for="OrderId" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="OrderDate" class="control-label"></label>

<input asp-for="OrderDate" type="date" class="form-control" />

<span asp-validation-for="OrderDate" class="text-danger"></span>

</div>
318

<div class="form-group">

<label asp-for="Amount" class="control-label"></label>

<input asp-for="Amount" class="form-control" />

<span asp-validation-for="Amount" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="CouponCode" class="control-label"></label>

<input asp-for="CouponCode" class="form-control" />

<span asp-validation-for="CouponCode" class="text-danger"></span>

</div>

@for (int i = 0; i < Model?.OrderItems?.Count; i++)

<div class="form-group">

<!-- Product Name -->

@Html.HiddenFor(m => m.OrderItems[i].ProductName)

<label asp-for="@Model.OrderItems[i].ProductName">Product Name:</label>

<span>

<b>@Model.OrderItems[i].ProductName</b>

</span>

<!-- Quantity -->

@Html.HiddenFor(m => m.OrderItems[i].Quantity)

<label asp-for="@Model.OrderItems[i].Quantity">Quantity:</label>

<span>

<b>@Model.OrderItems[i].Quantity</b>
319

</span>

<!-- Quantity -->

@Html.HiddenFor(m => m.OrderItems[i].UnitPrice)

<label asp-for="@Model.OrderItems[i].UnitPrice">UnitPrice:</label>

<span><b>@Model.OrderItems[i].UnitPrice</b></span>

</div>

<br />

<input type="submit" value="Place Order" class="btn btn-primary" />

</form>

</div>

</div>
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Website for a Library
Let’s go through a real-world example. Imagine you’re building a website for a library, and
you need to validate data for adding a new book.
Define the Model:
We’ll start with a Book model:
namespace FluentAPIDemo.Models

public class Book

public string ISBN { get; set; }

public string Title { get; set; }

public string Author { get; set; }

public DateTime PublishDate { get; set; }


320

public decimal Price { get; set; }

}
Set Up the Validator:
Now, we’ll set up validation:
 The ISBN should match a certain pattern.
 The title should be mandatory and have a maximum length.
 The author should also be mandatory.
 The publish date shouldn’t be in the future.
 The price should be positive.
Here’s the validator:
using FluentValidation;

namespace FluentAPIDemo.Models

public class BookValidator : AbstractValidator<Book>

public BookValidator()

RuleFor(b => b.ISBN)

.NotEmpty().WithMessage("ISBN is required.")

.Matches("^(97(8|9))?\\d{9}(\\d|X)$").WithMessage("Invalid ISBN format.");

RuleFor(b => b.Title)

.NotEmpty().WithMessage("Title is required.")

.MaximumLength(200).WithMessage("Title cannot exceed 200 characters.");

RuleFor(b => b.Author)

.NotEmpty().WithMessage("Author is required.");

RuleFor(b => b.PublishDate)

.NotEmpty().WithMessage("Publish date is required.")


321

.LessThan(DateTime.Now).WithMessage("Publish date cannot be in the future.");

RuleFor(b => b.Price)

.GreaterThan(0).WithMessage("Price should be positive.");

}
Understanding Matches(“^(97(8|9))?\\d{9}(\\d|X)$”):
The regular expression ^(97(8|9))?\d{9}(\d|X)$ is a pattern for validating ISBN-10 and ISBN-
13 numbers. Let’s break it down:
^: Matches the start of a string.
(97(8|9))?: This matches the prefixes ‘978’ or ‘979’ common for ISBN-13. The trailing “?”
means this part is optional, so it can also validate ISBN-10.
 (97: Matches the string ’97’.
 (8|9): Matches an ‘8’ or a ‘9’.
 ): Closes the group.
 ?: Specifies that the preceding element (in this case, the whole 97(8|9) group)
is optional.
\d{9}: Matches 9 digits. \d is a shorthand character class that matches any digit from 0 to 9.
(\d|X): Matches either a digit or the letter ‘X’. The ‘X’ is used in ISBN-10 numbers as a
checksum character.
$: Matches the end of a string.
So, the regex can match ISBN-13 numbers that start with ‘978’ or ‘979’, followed by 9 digits,
and ending with either a digit or ‘X’. It can also match ISBN-10 numbers, 9 digits followed by
a digit or ‘X’. Examples of valid matches would be:
 9781234567890 (an example of ISBN-13)
 123456789X (an example of ISBN-10 where ‘X’ is the checksum character)
This regex is versatile in matching both formats of the ISBN numbers, making it useful for
validation purposes.
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.

builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on the client side
322

builder.Services.AddTransient<IValidator<Book>, BookValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult AddBook()

return View();

[HttpPost]

public IActionResult AddBook(Book book)

if (!ModelState.IsValid)

return View(book); // Return with validation errors.

// Save the book to database or process further...

return RedirectToAction("Success");

public string Success()

{
323

return "Book Added Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add AddBook.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.Book

@{

ViewData["Title"] = "AddBook";

<h1>AddBook</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="AddBook" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group">

<label asp-for="Title" class="control-label"></label>

<input asp-for="Title" class="form-control" />

<span asp-validation-for="Title" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="Author" class="control-label"></label>

<input asp-for="Author" class="form-control" />


324

<span asp-validation-for="Author" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="ISBN" class="control-label"></label>

<input asp-for="ISBN" class="form-control" />

<span asp-validation-for="ISBN" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="PublishDate" class="control-label"></label>

<input asp-for="PublishDate" type="date" class="form-control" />

<span asp-validation-for="PublishDate" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="Price" class="control-label"></label>

<input asp-for="Price" type="number" class="form-control" />

<span asp-validation-for="Price" class="text-danger"></span>

</div>

<div class="form-group">

<input type="submit" value="Add Book" class="btn btn-primary" />

</div>

</form>

</div>

</div>
325

This application for adding a new book to a library system shows how to use
FluentValidation to handle real-world validation scenarios in ASP.NET Core MVC, providing
a clean and organized way to enforce data integrity.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Workshop Registration
Let’s understand another real-world example using FluentValidation in ASP.NET Core
MVC. In this example, we’ll consider an online registration system for a training workshop.
Define the Model:
We will use the following WorkshopRegistration model:
namespace FluentAPIDemo.Models

public class WorkshopRegistration

public string FullName { get; set; }

public string Email { get; set; }

public string PhoneNumber { get; set; }

public DateTime DateOfBirth { get; set; }

public string WorkshopTopic { get; set; }

}
Set Up the Validator:
For our WorkshopRegistration model, we want the following Validations.
 FullName should be mandatory and have a specific length range.
 Email should be a valid email format.
 PhoneNumber should match a pattern.
 DateOfBirth should ensure the registrant is above a certain age.
 WorkshopTopic should be selected from a predefined list.
The following is our validator:
using FluentValidation;

namespace FluentAPIDemo.Models

public class WorkshopRegistrationValidator : AbstractValidator<WorkshopRegistration>


326

public WorkshopRegistrationValidator()

RuleFor(r => r.FullName)

.NotEmpty().WithMessage("Full Name is required.")

.Length(5, 50).WithMessage("Full Name should be between 5 and 50 characters.");

RuleFor(r => r.Email)

.NotEmpty().WithMessage("Email is required.")

.EmailAddress().WithMessage("Enter a valid email address.");

RuleFor(r => r.PhoneNumber)

.NotEmpty().WithMessage("Phone Number is required.")

.Matches(@"^[0-9]{10}$").WithMessage("Enter a valid 10-digit phone number.");

RuleFor(r => r.DateOfBirth)

.NotEmpty().WithMessage("Date of Birth is required.")

.Must(BeAValidAge).WithMessage("Registrant must be at least 18 years old.");

RuleFor(r => r.WorkshopTopic)

.Must(topic => new List<string> { "Web Development", "Data Science", "AI", "Design"
}.Contains(topic))

.WithMessage("Invalid workshop topic selected.");

private bool BeAValidAge(DateTime date)

int age = DateTime.Today.Year - date.Year;

if (date > DateTime.Today.AddYears(-age))


327

age--;

return age >= 18;

}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.

builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on client side

builder.Services.AddTransient<IValidator<WorkshopRegistration>,
WorkshopRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult Register()


328

return View();

[HttpPost]

public IActionResult Register(WorkshopRegistration registration)

if (!ModelState.IsValid)

return View(registration); // Return with validation errors.

// Process the registration...

return RedirectToAction("Success");

public string Success()

return "Registration Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy
and paste the following code.
@model FluentAPIDemo.Models.WorkshopRegistration

@{

ViewData["Title"] = "Register";
329

<h1>User Register</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="Register" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group">

<label asp-for="FullName" class="control-label"></label>

<input asp-for="FullName" class="form-control" />

<span asp-validation-for="FullName" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="PhoneNumber" class="control-label"></label>

<input asp-for="PhoneNumber" class="form-control" />

<span asp-validation-for="PhoneNumber" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="DateOfBirth" class="control-label"></label>


330

<input asp-for="DateOfBirth" type="date" class="form-control" />

<span asp-validation-for="DateOfBirth" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="WorkshopTopic" class="control-label"></label>

<select asp-for="WorkshopTopic">

<option value="Web Development">Web Development</option>

<option value="Data Science">Data Science</option>

<option value="AI">AI</option>

<option value="Design">Design</option>

<option value="Invalid">Invalid</option>

</select>

<span asp-validation-for="WorkshopTopic"></span>

</div>

<div class="form-group">

<input type="submit" value="Register" class="btn btn-primary" />

</div>

</form>

</div>

</div>
This example demonstrates how FluentValidation can handle various validation needs for
an online workshop registration system in an ASP.NET Core MVC application. It provides a
systematic and organized approach, ensuring a robust registration process.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC:
Hotel Reservation
Let us understand another real-world example using FluentValidation in ASP.NET Core
MVC. Now, we will consider a scenario where a user can reserve a room at a hotel.
331

Define the Model:


Here’s our HotelReservation model:
namespace FluentAPIDemo.Models

public class HotelReservation

public string GuestName { get; set; }

public string Email { get; set; }

public DateTime CheckInDate { get; set; }

public int NumberOfNights { get; set; }

public string RoomType { get; set; }

}
Set Up the Validator:
For our HotelReservation model:
 GuestName should be mandatory and have a specific length.
 Email should be a valid email format.
 CheckInDate shouldn’t be in the past.
 NumberOfNights should be positive and below a certain limit (e.g., a
maximum of 30 nights).
 RoomType should be selected from a predefined list.
Here’s the validator:
using FluentValidation;

namespace FluentAPIDemo.Models

public class HotelReservationValidator : AbstractValidator<HotelReservation>

public HotelReservationValidator()

{
332

RuleFor(r => r.GuestName)

.NotEmpty().WithMessage("Guest Name is required.")

.Length(2, 100).WithMessage("Guest Name should be between 2 and 100 characters.");

RuleFor(r => r.Email)

.NotEmpty().WithMessage("Email is required.")

.EmailAddress().WithMessage("Enter a valid email address.");

RuleFor(r => r.CheckInDate)

.GreaterThan(DateTime.Today).WithMessage("Check-In date cannot be in the past.")

.LessThan(DateTime.Today.AddYears(1)).WithMessage("Reservations can only be made up


to one year in advance.");

RuleFor(r => r.NumberOfNights)

.GreaterThan(0).WithMessage("You must reserve at least one night.")

.LessThanOrEqualTo(30).WithMessage("You can reserve a maximum of 30 nights.");

RuleFor(r => r.RoomType)

.Must(type => new List<string> { "Single", "Double", "Suite" }.Contains(type))

.WithMessage("Invalid room type selected.");

}
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the
Model and corresponding validator.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation
pipeline.

builder.Services.AddFluentValidationAutoValidation();

//Enables integration between FluentValidation and ASP.NET client-side validation.


333

builder.Services.AddFluentValidationClientsideAdapters();

//Registering Model and Validator to show the error message on the client side

builder.Services.AddTransient<IValidator<HotelReservation>,
HotelReservationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FluentAPIDemo.Controllers

public class HomeController : Controller

public IActionResult BookRoom()

return View();

[HttpPost]

public IActionResult BookRoom(HotelReservation reservation)

if (!ModelState.IsValid)

return View(reservation); // Return with validation errors.

// Process the reservation...

return RedirectToAction("Success");
334

public string Success()

return "Booking Successful";

}
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add BookRoom.cshtml view and
copy and paste the following code.
@model FluentAPIDemo.Models.HotelReservation

@{

ViewData["Title"] = "BookRoom";

<h1>BookRoom</h1>

<hr />

<div class="row">

<div class="col-md-4">

<form asp-action="BookRoom" asp-controller="Home" method="post">

<div asp-validation-summary="All" class="text-danger"></div>

<div class="form-group">

<label asp-for="GuestName" class="control-label"></label>

<input asp-for="GuestName" class="form-control" />

<span asp-validation-for="GuestName" class="text-danger"></span>

</div>
335

<div class="form-group">

<label asp-for="Email" class="control-label"></label>

<input asp-for="Email" class="form-control" />

<span asp-validation-for="Email" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="CheckInDate" class="control-label"></label>

<input asp-for="CheckInDate" type="date" class="form-control" />

<span asp-validation-for="CheckInDate" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="NumberOfNights" class="control-label"></label>

<input asp-for="NumberOfNights" type="number" class="form-control" />

<span asp-validation-for="NumberOfNights" class="text-danger"></span>

</div>

<div class="form-group">

<label asp-for="RoomType" class="control-label"></label>

<select asp-for="RoomType">

<option value="Single">Single</option>

<option value="Double">Double</option>

<option value="Suite">Suite</option>

</select>

<span asp-validation-for="RoomType"></span>

</div>
336

<div class="form-group">

<input type="submit" value="Book Room" class="btn btn-primary" />

</div>

</form>

</div>

</div>
This example demonstrates how Fluent API Validation can effectively handle different
validation scenarios for a hotel room booking system in an ASP.NET Core MVC application.
It provides a structured and organized approach, ensuring a user-friendly booking
experience.

Data Annotations vs. Fluent API in ASP.NET Core MVC


In this article, I will discuss Data Annotations vs. Fluent API in ASP.NET Core
MVC Applications with Examples. Please read our previous article discussing Real-Time
Examples of Fluent API Validations in ASP.NET Core MVC.
What are Data Annotations in ASP.NET Core?
In ASP.NET Core, Data Annotations are attributes that can be applied to model classes and
properties to configure and validate data. These attributes are not exclusive to ASP.NET
Core and are actually a part of the .NET platform. However, they are frequently used in
ASP.NET Core applications for Entity Framework Core configurations and model validation.
Data Annotations Serve Multiple Purposes:
Validation: By using validation attributes, you can define the rules that a property must
satisfy. A validation error is generated if the property does not meet these rules. For
example:
 [Required]: Indicates that a property must have a value.
 [StringLength(50)]: The string value should be at most 50 characters long.
 [Range(1, 100)]: A numerical value should be between 1 and 100.
Display and Formatting: You can control how data is displayed in views.

Traveling across Quang Binh to stunning cinematic locations00:13 / 03:1710 Sec


 [Display(Name=”First Name”)]: Specifies the display name for a property.
 [DataType(DataType.Date)]: Specifies that a property should be treated as a
date, which can influence formatting and display.
Entity Framework Core Configurations: While the Fluent API provides more
comprehensive configuration options, Data Annotations can also influence how a model is
mapped to a database using EF Core.
 [Table(“MyTableName”)]: Maps an entity to a specific database table name.
 [Key]: Designates a property as the primary key.
337

 [Column(TypeName=”varchar(50)”)]: Specifies the column type in the


database.
What are Fluent API Validations in ASP.NET Core?
In the context of ASP.NET Core, developers often use the term “Fluent API validations” to
describe the implementation of validation rules using the FluentValidation library. This
external library provides a way to define validation rules using the Fluent Interface Design
Pattern or you can say using the concept called Method Chaining where we can invoke
multiple Fluent API Validator methods one after another using the dot (.) operator.
Here’s a brief overview of FluentValidation and its features:
 Fluent Interface: As the name suggests, FluentValidation employs a fluent
interface, allowing you to chain methods together for more readable validation
rules.
 Separation of Concerns: With FluentValidation, validation rules are typically
defined in separate validator classes instead of being decorated directly on
model properties. This leads to a cleaner separation between model definition
and validation logic.
 Powerful and Expressive: It allows for more complex validation scenarios,
including conditional validation, collection validation, and cross-property
validation.
 Integration with ASP.NET Core: FluentValidation offers seamless integration
with ASP.NET Core’s model binding, so if a model fails validation, the
ModelState will be marked as invalid, much like it would be with Data
Annotations.
 Custom Validators: You can create custom validation rules if the built-in
ones don’t cover your needs.
Data Annotations vs. Fluent API for Validations in ASP.NET Core MVC
In ASP.NET Core MVC, both Data Annotations and Fluent Validation (often implemented
via the FluentValidation library) are mechanisms to enforce validation rules on models.
While Data Annotations are provided out of the box in ASP.NET Core, Fluent Validation is a
third-party library that offers a more powerful and fluent interface for validation.
Let’s compare these two approaches for validation:
Syntax and Style:
Data Annotations: Uses attributes placed directly on model properties.
public class Person

[Required]

public string Name { get; set; }

[Range(0, 150)]

public int Age { get; set; }

}
Fluent API (FluentValidation): Uses a chained method approach.
338

public class PersonValidator : AbstractValidator<Person>

public PersonValidator()

RuleFor(x => x.Name).NotEmpty();

RuleFor(x => x.Age).InclusiveBetween(0, 150);

}
Expressiveness:
 Data Annotations: Limited expressiveness and can be less useful for
complex rules.
 Fluent API (FluentValidation): Highly expressive, allowing more complex
validation rules in a more readable manner.
Extensibility:
 Data Annotations: To create custom validation, you’d typically need to create
a new attribute class.
 Fluent API (FluentValidation): Easily supports custom validation functions
without needing to create new classes or attributes.
Integration:
 Data Annotations: Integrated out-of-the-box with ASP.NET Core model
binding.
 Fluent API (FluentValidation): Requires setting up integration with ASP.NET
Core, but the FluentValidation library provides integration packages and
extensions to make this process easier.
Performance:
 Data Annotations: Slightly faster for basic scenarios since there’s no
additional overhead.
 Fluent API (FluentValidation): It might have a minor performance overhead
due to its more dynamic nature, but this isn’t very important in most real-world
scenarios.
Dependency:
 Data Annotations: No external libraries are required; it’s built into .NET.
 Fluent API (FluentValidation): Requires adding the FluentValidation NuGet
package.
Maintenance and Readability:
 Data Annotations: The code can become cluttered for models with
numerous attributes, impacting readability.
 Fluent API (FluentValidation): Centralizes validation rules, making it
potentially easier to read and manage, especially when complex rules exist.
339

While both mechanisms offer ways to ensure model validity, the right choice depends on
the specific requirements of your application. For simple validation needs, Data Annotations
might be better. However, if you need more complex validation logic, especially cross-
property validations, or prefer a fluent API approach, FluentValidation might be the better
choice.
When to use Data Annotations for Validations in ASP.NET Core?
Data Annotations in ASP.NET Core provide a straightforward way to define validation rules
on model properties using attributes. While they’re useful and convenient, they may not
always be the best choice for every scenario. Here’s when you might consider using Data
Annotations for validations:
 Simplicity and Rapid Development: If you have simple validation
requirements and want to quickly add validations without setting up additional
libraries or configurations, Data Annotations are ideal.
 Integrated with .NET: Since Data Annotations are part of the .NET platform,
there’s no need to introduce third-party libraries, which can be an advantage
in environments where adding external dependencies is discouraged.
 Clear Visual Indication: By looking at the model properties, developers can
easily identify the validation rules applied due to the presence of attributes.
This can help in quickly understanding the expected format or constraints on
data.
 Client-Side Validation: When combined with ASP.NET Core MVC tag
helpers in Razor views, certain Data Annotations can automatically generate
client-side validation. For example, if you have an [Required] attribute on a
model property, the generated form field will have client-side validation,
ensuring the field isn’t left empty.
 Integrated with Entity Framework Core (EF Core): If you’re using EF Core
for data access, some Data Annotations (like [Required], [StringLength]) will
also influence the database schema during migrations or database creation.
 Built-in Validations: For common validation scenarios like checking for
required fields, maximum string length, patterns (using [RegularExpression]),
or range validations, Data Annotations offer out-of-the-box attributes, so
there’s no need to write custom logic.
When NOT to use Data Annotations?
 Complex Validation Rules: If you have intricate validation scenarios,
especially those that involve multiple properties (cross-property validation),
Data Annotations can become cumbersome. FluentValidation or other
libraries might be more appropriate in this case.
 Separation of Concerns: If you prefer to keep your validation logic separate
from your model or entity classes, using a library like FluentValidation, which
defines validation in separate classes, can be beneficial.
 Customization and Extensibility: While you can create custom validation
attributes with Data Annotations, doing so might feel less natural or more
verbose than library customization options like FluentValidation.
 Reusability: If you reuse certain complex validation logic across different
models or parts of the application, external validation libraries might provide a
more modular approach.
So, Data Annotations are a great choice for straightforward validation scenarios, especially
when rapid development and simplicity are priorities. However, considering external
340

libraries like FluentValidation might be beneficial for more advanced or complex validation
needs.
When to use Fluent API for Validations in ASP.NET Core?
When referring to the “Fluent API” in the context of validations in ASP.NET Core, it’s often
associated with the FluentValidation library. FluentValidation offers a powerful, expressive,
and chainable API for defining validation rules. Here’s when you might consider using
FluentValidation (Fluent API) for validations:
 Complex Validation Scenarios: FluentValidation excels in scenarios
requiring intricate validation logic. It easily supports cross-property
validations, conditional validations, and collection validations, among others.
 Separation of Concerns: With FluentValidation, validation rules are defined
in separate validator classes. This ensures that the model or entity classes
remain clean and validation logic is decoupled, promoting better separation of
concerns.
 Reusability and Modularity: With FluentValidation, you can define reusable
validation rules (as methods or extensions) and apply them across multiple
models or properties.
 Custom Validation Logic: Creating custom validators in FluentValidation is
straightforward, and the fluent API makes it intuitive to apply custom logic.
 Chainable Validation Rules: FluentValidation allows you to fluently chain
multiple rules for a single property, making the rules more readable and
expressive.
 Dynamic Error Messages: FluentValidation allows the dynamic generation
of error messages based on property values, making it flexible for scenarios
where static error messages (as in Data Annotations) are insufficient.
 Integration with External Data Sources: If your validation requires
integration with external data sources (like databases or APIs),
FluentValidation provides asynchronous validators and the ability to inject
dependencies into validators, which can be highly beneficial.
 Testing and Maintenance: Since FluentValidation rules are defined in
separate classes, it becomes easier to write unit tests for these validators,
ensuring that validation logic is correct and making maintenance easier.
 Granular Control Over Validation Process: FluentValidation provides more
control over the validation process, like choosing when to stop rule execution
after the first failure, defining the severity of failures, and more.
When NOT to use FluentValidation?
 Simplicity Needed: For very basic validation scenarios where you want to
check if a field is required or if a string length is within a certain range,
introducing FluentValidation might be overkill. Data Annotations could be
sufficient in such cases.
 Avoiding External Dependencies: If you’re trying to minimize external
libraries or dependencies, then introducing FluentValidation might not be
suitable.
 Tight Integration with Entity Framework Core (EF Core): While Data
Annotations used for validation can also impact EF Core configurations (like
database schema generation), FluentValidation is purely for validation and
doesn’t affect EF Core configurations.
341

So, FluentValidation (Fluent API) is a powerful tool for validations in ASP.NET Core,
especially when dealing with complex scenarios, seeking a clear separation of concerns, or
when more control over the validation process is needed. However, built-in mechanisms like
Data Annotations might be more appropriate for simple validations.

Cookies in ASP.NET Core MVC


In this article, I will discuss Cookies in ASP.NET Core MVC Applications with
Examples. The communication between a server and a client on the web uses the HTTP
protocol, which is a Stateless Protocol. This means that information will not be
automatically shared between two requests by a user. To share the information between
multiple requests, we need to use cookies or sessions. This is useful in situations like
keeping a user logged in between requests. Nowadays, most server-side technologies,
including the ASP.NET Core MVC framework, have built-in support for cookies and
sessions. Let us proceed and understand the Cookies in detail.
What is a Cookie?
A cookie is a small text file that a website stores on a user’s computer or mobile device
when they visit the site. This file contains information that can be used to identify the user,
such as user ID, user name, login status, token, etc, and is sent back to the website with
every subsequent request. Typically, it is used to tell the web server if two requests are
coming from the same web browser.
Cookies are stored in the form of key-value pairs, and you can use the keys to read, write,
or delete cookies. For a better understanding of how cookies work in a web application,
please have a look at the following diagram.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:17 / 03:1710 Sec
Cookies work as follows:
 First, the client (Web Browser) will send the HTTP Request to the Server.
342

 Once the server receives the request, it will process the request and generate
the response. Along with the generated response, it will create the cookie and
send it to the client within the response header. You can also set multiple
cookies as per your requirement. For each cookie, it will add one Set-Cookie
in the response header.
 Once the client (web browser) receives the response, from the next request
onwards (as long as the cookie is not expired and not deleted), the client will
send the HTTP Request along with the cookie in the request header. The
client will send one Cookie in the request header containing all cookies data.
 Once the server receives the request, it will check the cookie and accordingly
process the request.
Types of Cookies:
There are two types of Cookies. They are as follows:
 Persistent Cookies: Persistent cookies are long-term cookies that can be
stored across multiple sessions. They retain information such as login
credentials, allowing users to have a personalized experience when they
revisit a website.
 Non-Persistent Cookies: Non-Persistent Cookies are temporary cookies
stored on the user’s computer while browsing a website. They are typically
used to maintain a session state and are destroyed when the client closes
their browser or navigates away from the web page.
Note: In this article, I will show the example using Persistent Cookies. In our next article, I
will discuss the example using Non-Persistent Cookies and then discuss the differences
between them.
How do you Write, Read, and Delete Cookies in ASP.NET Core MVC?
ASP.NET Core MVC provides built-in support for working with cookies to enable various
scenarios, such as authentication, session management, and tracking user preferences.
Writing a Cookie in ASP.NET Core MVC:
To create a cookie in ASP.NET Core MVC, create an instance of the CookieOptions class,
set the expiry date using the Expires property, and then add the cookie
to Response.Cookies collection using the Append method with name, value, and
CookieOptions object as parameters as follows.
CookieOptions options = new CookieOptions();
options.Expires = DateTime.Now.AddDays(7);
Response.Cookies.Append(“UserId”, “1234567”, options);
Response.Cookies.Append(“UserName”, “pranaya@dotnettutotials.net”, options);
The CookieOptions class provides additional properties that can be set when creating a
cookie, including:
1. Domain: Gets or sets the domain to associate the cookie with.
2. Expiration time: Gets or sets the expiration date and time for the cookie.
3. Path: Gets or sets the cookie path. Creates a default cookie with a path of ‘/.’
4. Secure: Gets or sets a value indicating whether to transmit the cookie using
Secure Sockets Layer (SSL) over HTTPS.
5. HttpOnly: Gets or sets a value that indicates whether a cookie is accessible
by client-side script. If its value is set to true, then it can’t be accessed from
JavaScript.
6. MaxAge: Gets or sets the maximum age for the cookie.
343

7. IsEssential: Indicates if this cookie is essential for the application to function


correctly. If true, then consent policy checks may be bypassed. The default
value is false.
Reading a Cookie in ASP.NET Core MVC:
We need to use the Request.Cookies collection to read a cookie in ASP.NET Core MVC,
and we need to pass the key as follows:
string? UserName = Request.Cookies[“UserName”];
int? UserId = Convert.ToInt32(Request.Cookies[“UserId”]);
Deleting a Cookie in ASP.NET Core MVC:
We need to use Request.Cookies.Delete to delete a cookie in ASP.NET Core MVC by
passing the key name as follows.
Response.Cookies.Delete(“UserId”);
Response.Cookies.Delete(“UserName”);
Modifying the Home Controller:
Next, modify the HomeController as follows. As you can see, we have created two constant
variables to store the cookie keys. We store user data in the cookie object within the Index
Action method. Further, if you notice, we are accessing the cookie data from the About
action method.
using Microsoft.AspNetCore.Mvc;

namespace SampleMVCWeb.Controllers

public class HomeController : Controller

const string CookieUserId = "UserId";

const string CookieUserName = "UserName";

public IActionResult Index()

//Let us assume the User is logged in and we need to store the user information in the cookie

CookieOptions options = new CookieOptions();

options.Expires = DateTime.Now.AddDays(7);

Response.Cookies.Append(CookieUserId, "1234567", options);

Response.Cookies.Append(CookieUserName, "pranaya@dotnettutotials.net", options);

return View();
344

public string About()

//Accessing the Cookie Data inside a Method

string? UserName = Request.Cookies[CookieUserName];

int? UserId = Convert.ToInt32(Request.Cookies[CookieUserId]);

string Message = $"UserName: {UserName}, UserId: {UserId}";

return Message;

public IActionResult Privacy()

return View();

public string DeleteCookie()

// Delete the cookie from the browser.

Response.Cookies.Delete(CookieUserId);

Response.Cookies.Delete(CookieUserName);

return "Cookies are Deleted";

}
How do you Access the Cookie Object in a View?
To use Cookies inside a View, we need to inject the IHttpContextAccessor service into our
view and use it to get the HttpContext and Request objects from that. For this, we need to
345

add the following two statements in our view. The first statement adds the namespace and
the second statement injects the IHttpContextAccessor. Using this HttpContextAccessor,
we can access the HttpContext and Session object.
@using Microsoft.AspNetCore.Http;
@inject IHttpContextAccessor HttpContextAccessor
Modifying the Index.cshtml view:
@using Microsoft.AspNetCore.Http;

@inject IHttpContextAccessor HttpContextAccessor

@{

ViewData["Title"] = "Home Page";

<div class="text-left">

<b>User Name:</b> @HttpContextAccessor?.HttpContext?.Request.Cookies["UserName"]

<br />

<b>User Id:</b> @HttpContextAccessor?.HttpContext?.Request.Cookies["UserId"]

</div>
Modifying the Privacy.cshtml view:
@using Microsoft.AspNetCore.Http;

@inject IHttpContextAccessor HttpContextAccessor

@{

ViewData["Title"] = "Privacy Policy";

<h1>@ViewData["Title"]</h1>

<div class="text-left">

<b>User Name:</b> @HttpContextAccessor?.HttpContext?.Request.Cookies["UserName"]

<br />

<b>User Id:</b> @HttpContextAccessor?.HttpContext?.Request.Cookies["UserId"]


346

</div>
If you run the application with the above changes, you will get the following exception: We
use the HttpContextAccessor service but have not yet configured it into the built-in
IOC container.

Now, we need to configure the HttpContextAccessor service within the Program class. The
following code does the same.
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So, modify the Main method of the Program class as follows:
namespace SampleMVCWeb

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

//Adding the IHttpContextAccessor servive to the Dependency Injection IOC Container

builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

{
347

app.UseExceptionHandler("/Home/Error");

// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.

app.UseHsts();

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

}
Now, run the application, and by default, it will execute the Index Action Method of the
Home controller. Then, it will render the Index view where you can see the cookie data, as
shown in the image below.
348

Inspecting the Cookies using Browser Developer Tool:


Now, open the browser developer tool, go to the Network tab, and inspect the request; the
server will send two cookies in the response header, as shown in the image below. The
cookie contains the key name, value, and expiration date. As you can see, cookies store the
user information in plain text, which is not good from the security point of view.

If you visit the Privacy page, it will render the cookie data, as shown in the image below.
349

Now, if you go to the Network tab and inspect the request using the browser developer tool,
you will see that along with the request, the web browser is now sending the cookie in the
request header, as shown in the image below. This will happen for each and every request
sent from the client to the server until the cookie is not expired or deleted.

If you visit the About page, it will also render the session data, as shown in the image
below.
350

If you visit the DeleteCookie page, it will delete the cookies, and you will get the following
message, as shown in the image below.

Now, if you verify the response header, you will see that the cookie values are being
deleted, as shown in the image below.

If you visit the About and Privacy page, you will not see the cookie data shown in the image
below.
351

Advantages of Using Cookies in ASP.NET Core MVC:


1. State Management: Cookies enable you to maintain state across multiple
HTTP requests, allowing you to track user sessions, authentication status,
and user preferences.
2. Session Persistence: Cookies can persist information across browser
sessions, allowing users to maintain their data even after they close and
reopen the browser.
3. Authentication and Authorization: Cookies are commonly used to
implement authentication mechanisms, allowing you to maintain user
sessions and keep users logged in.
4. Ease of Use: Cookies are simple to implement and use. ASP.NET Core
provides easy-to-use APIs for working with cookies, making reading, writing,
and managing cookie data straightforward.
5. Server Resource Savings: Storing user-related data on the client side
reduces the need for the server to manage the session state, potentially
improving server performance and scalability.
Disadvantages of Using Cookies in ASP.NET Core MVC:
1. Data Size Limit: Cookies have size limitations (usually around 4KB), which
may restrict the amount of data you can store.
2. Security Concerns: Cookies are stored on the client side and can be
tampered with by users.
3. Network Overhead: Cookies are sent with every HTTP request, increasing
network traffic. This can impact performance, especially if large amounts of
data are being transmitted.
4. Cross-Site Scripting (XSS) Vulnerabilities: If not properly secured, cookies
can be vulnerable to XSS attacks, where malicious scripts are injected into a
web application.
5. Browser Compatibility: While cookies are widely supported, users can
disable or restrict them in their browser.
When should we use Cookies in ASP.NET Core MVC?
Cookies in ASP.NET Core MVC can be used in various scenarios to enhance user
experience, manage state, and provide personalized interactions. Here are some situations
where using cookies is appropriate:
352

1. Authentication and Authorization: Cookies are commonly used for


implementing authentication and maintaining user sessions. When a user
logs in, a cookie with a secure token or identifier can be issued, and
subsequent requests can include the cookie to authenticate the user and
grant access to authorized resources.
2. Remember Me Functionality: Cookies can be used to remember a user’s
login credentials between sessions, offering a “Remember Me” feature that
enhances user convenience.
3. Shopping Carts: Sessions are often employed to manage shopping cart
data. You can store information about items added to the cart and their
quantities, ensuring that the cart contents are retained between page views
and interactions.
4. Form Data Persistence: Sessions can temporarily store form data between
multiple steps of a multi-page process. This is useful for scenarios like multi-
step forms or wizards.
5. Single Sign-On (SSO): Cookies can be utilized in single sign-on scenarios to
allow users to authenticate once and access multiple applications without
repeated logins.

How to Encrypt Cookies in ASP.NET Core MVC


In this article, I will discuss How to Encrypt Cookies in ASP.NET Core MVC Applications
with Examples. Please read our previous article discussing Cookies in ASP.NET Core
MVC.
Encrypt Cookies in ASP.NET Core MVC
Encrypting cookies in ASP.NET Core MVC is essential for enhancing the security of your
application. This ensures that sensitive information within cookies cannot be easily read or
tampered with by unauthorized users. ASP.NET Core provides built-in mechanisms to
implement cookie encryption. Using Data Protection API, we can encrypt the cookies in an
ASP.NET Core MVC application:
What is Data Protection API?
The primary goal of the Data Protection API is to protect sensitive data, especially when it
needs to be temporarily stored in an insecure location, like in cookies or form fields.
Common use cases include protecting authentication tokens, session states, or any other
sensitive information that must be preserved between requests.
How Do We Use Data Protection API in ASP.NET Core MVC?
ASP.NET Core uses the Data Protection API to provide cookie encryption. You generally do
not need to manually encrypt cookies, as the framework handles it for you. But, if you create
custom cookies, you must manually handle the encryption. You need to configure the Data
Protection API in your Program.cs file as follows:
353

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
builder.Services.AddDataProtection();
How Do We Encrypt Custom Cookies in ASP.NET Core MVC?
If you are creating custom cookies and want to ensure they are encrypted, you need to use
the Data Protection API manually. Let us understand this with an example. Let us first
create a class file named MyCookieService.cs within the Models folder and then copy and
paste the following code:
using Microsoft.AspNetCore.DataProtection;

namespace SampleMVCWeb.Models

public class MyCookieService

private readonly IDataProtector _protector;

public MyCookieService(IDataProtectionProvider dataProtectionProvider)

//MyCookieProtector must be a unique string value for each Data Protection Provider

_protector = dataProtectionProvider.CreateProtector("MyCookieProtector");

//This method will convert the Plain Data to Encrypted value

public string Protect(string cookieValue)

return _protector.Protect(cookieValue);

//This method will convert the Encrypted Data to Plain value

public string Unprotect(string protectedCookieValue)

{
354

return _protector.Unprotect(protectedCookieValue);

}
In ASP.NET Core, the Data Protection API provides CreateProtector, Protect, and
Unprotect methods, which we can use to protect the cookies. The use of each method is as
follows:
 CreateProtector Method: The CreateProtector method creates an instance
of IDataProtector, which is then used to encrypt and decrypt data. You need
to specify a purpose string when calling CreateProtector, which acts as a way
to isolate protectors from each other.
 Protect Method: The Protect method is used to encrypt data. You need to
pass the plaintext data to this method, which returns the encrypted data.
 Unprotect Method: The Unprotect method decrypts encrypted data. It will
throw an exception if the data cannot be decrypted (e.g., if it has been
tampered with or the protector was created for a different purpose).
Storing Cookies
When storing cookies in the response header, use the encrypted value. First, encrypt the
value using DataProtector and then store the encrypted value in the Cookies. The syntax to
encrypt the cookie is given below:
//Encrypt the Value using Data Protection API

string encryptedValue = myCookieService.Protect("Cookie Value");

//Store the Encrypted Cookie Value in the Response Header

Response.Cookies.Append("myCookie", encryptedValue, cookieOptions);


Reading Cookies
When reading the cookie, decrypt the value. First, read the encrypted value from the cookie
and then use the DataProtector to decrypt the value. The syntax to decrypt the cookie is
given below:
//Fetch the Encrypted Cookie Value from the Response Header

string encryptedValue = Request.Cookies["myCookie"];

//Decrypt the Encryoted Value using the Data Protection API

string decryptedValue = myCookieService.Unprotect(encryptedValue);


Configuring the Service:
355

Next, we need to configure the MyCookieService within the built-in dependency injection
container. So, please add the following code to the Program class. Here, we need to pass
the concrete class name to the AddSingleton method.
builder.Services.AddSingleton<MyCookieService>();
Modifying Home Controller:
Next, modify the Home Controller as follows. First, we inject the MyCookieService instance
through the constructor, and then, using this MyCookieService instance, we encrypt and
decrypt the cookies. Further, we have stored the retrieved cookie values in the ViewBag,
which will be displayed in the UI. This is because we don’t want to write the logic to fetch
the encrypted data from the cookie and decrypt the value in the View file.
using Microsoft.AspNetCore.Mvc;

using SampleMVCWeb.Models;

namespace SampleMVCWeb.Controllers

public class HomeController : Controller

const string CookieUserId = "UserId";

const string CookieUserName = "UserName";

MyCookieService _myCookieService;

public HomeController(MyCookieService myCookieService)

_myCookieService = myCookieService;

public IActionResult Index()

//Let us assume the User is logged in and we need to store the user information in the cookie

var cookieOptions = new CookieOptions

HttpOnly = true,
356

Secure = true,

SameSite = SameSiteMode.Strict,

Expires = DateTime.Now.AddDays(7)

};

//Encrypt the Cookie Value

string encryptedUserId = _myCookieService.Protect("1234567");

//Store the Encrypted value in the Cookies Response Header

Response.Cookies.Append(CookieUserId, encryptedUserId, cookieOptions);

//Encrypt the Cookie Value

string encryptedUserName = _myCookieService.Protect("pranaya@dotnettutotials.net");

//Store the Encrypted value in the Cookies Response Header

Response.Cookies.Append(CookieUserName, encryptedUserName, cookieOptions);

//If you are coming to this page later,

//it will also fetch the data from the Cookies Response Header

string? encryptedUserNameValue = Request.Cookies[CookieUserName];

if (encryptedUserNameValue != null)

ViewBag.UserName = _myCookieService.Unprotect(encryptedUserNameValue);

};

string? encryptedUserIdValue = Request.Cookies[CookieUserId];

if (encryptedUserIdValue != null)

ViewBag.UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));

}
357

return View();

public string About()

try

string? UserName = null;

//Fetch the Encrypted Cookie from the Request Header

string? encryptedUserNameValue = Request.Cookies[CookieUserName];

if (encryptedUserNameValue != null)

//Decrypt the Encrypted Value

UserName = _myCookieService.Unprotect(encryptedUserNameValue);

};

int? UserId = null;

//Fetch the Encrypted Cookie from the Request Header

string? encryptedUserIdValue = Request.Cookies[CookieUserId];

if (encryptedUserIdValue != null)

//Decrypt the Encrypted Value

UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));

string Message = $"UserName: {UserName}, UserId: {UserId}";

return Message;
358

catch (Exception ex)

return $"Error Occurred: {ex.Message}";

public IActionResult Privacy()

//Fetch the Encrypted Cookie from the Request Header

string? encryptedUserNameValue = Request.Cookies[CookieUserName];

if (encryptedUserNameValue != null)

//Store the Decrypted Value in the ViewBag which we will display in the UI

ViewBag.UserName = _myCookieService.Unprotect(encryptedUserNameValue);

};

//Fetch the Encrypted Cookie from the Request Header

string? encryptedUserIdValue = Request.Cookies[CookieUserId];

if (encryptedUserIdValue != null)

//Store the Decrypted Value in the ViewBag which we will display in the UI

ViewBag.UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));

return View();

}
359

public string DeleteCookie()

// Delete the Cookie From the Response Header, i.e., from the Browser.

Response.Cookies.Delete(CookieUserId);

Response.Cookies.Delete(CookieUserName);

return "Cookies are Deleted";

}
CookieOptions Class in ASP.NET Core MVC
 HttpOnly: The HttpOnly property specifies whether the cookie should be
accessible only by the server. When set to true, the cookie cannot be
accessed through client-side scripts. This is an important security feature that
helps prevent cross-site scripting (XSS) attacks by not allowing client-side
script access to the cookie. Default Value: false
 Secure: The Secure property indicates that the cookie should be sent only
over HTTPS. This security measure helps prevent attackers from intercepting
the cookie in a man-in-the-middle attack. Default Value: false
 SameSite: The SameSite property controls the same-site and cross-site
cookie-sending behavior. It helps to protect against cross-site request forgery
(CSRF) attacks. Default Value: SameSiteMode.Lax (Note: This can vary
based on the version of ASP.NET Core and browser behavior.)
 Expires: The Expires property sets the expiration date and time for the
cookie. After this time, the cookie will no longer be sent to the server. This
property is essential for controlling the lifecycle of your cookie. Default Value:
null. By default, cookies are session cookies, which means they last only as
long as the browser session.
Modifying Index.cshtml View
@{

ViewData["Title"] = "Home Page";

<div class="text-left">

<b>User Name:</b> @ViewBag.UserName


360

<br />

<b>User Id:</b> @ViewBag.UserId

</div>
Modifying Privacy.cshtml View
@{

ViewData["Title"] = "Privacy Policy";

<h1>@ViewData["Title"]</h1>

<div class="text-left">

<b>User Name:</b> @ViewBag.UserName

<br />

<b>User Id:</b> @ViewBag.UserId

</div>
With the above changes in place, run the application, and now you should see the cookies
are transmitted over the network in encrypted format, as shown in the below image:

Configuring Cookie Options Globally:


If you want to store the cookies using multiple action methods and you want multiple action
methods to follow the same set of cookie option settings, then you can move such settings
to the Program.cs class file. So, please add the following code to the Program.cs class file:
builder.Services.ConfigureApplicationCookie(options =>

{
361

// Cookie settings

options.Cookie.HttpOnly = true;

options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

options.Cookie.IsEssential = true; // Make the cookie essential

options.Cookie.Expiration = TimeSpan.FromDays(1);

});
Now, you need to use the other version of the Append method to set the cookie, which does
not take the cookie option object. So, modify the Index action method of the Home
Controller class as follows:
public IActionResult Index()

//Let us assume the User is logged in and we need to store the user information in the cookie

//var cookieOptions = new CookieOptions

//{

// HttpOnly = true,

// Secure = true,

// SameSite = SameSiteMode.Strict,

// Expires = DateTime.Now.AddDays(7)

//};

//Encrypt the Cookie Value

string encryptedUserId = _myCookieService.Protect("1234567");

//Store the Encrypted value in the Cookies Response Header

//Response.Cookies.Append(CookieUserId, encryptedUserId, cookieOptions);

Response.Cookies.Append(CookieUserId, encryptedUserId);

//Encrypt the Cookie Value


362

string encryptedUserName = _myCookieService.Protect("pranaya@dotnettutotials.net");

//Store the Encrypted value in the Cookies Response Header

//Response.Cookies.Append(CookieUserName, encryptedUserName, cookieOptions);

Response.Cookies.Append(CookieUserName, encryptedUserName);

//If you are coming to this page later,

//it will also fetch the data from the Cookies Response Header

string? encryptedUserNameValue = Request.Cookies[CookieUserName];

if (encryptedUserNameValue != null)

ViewBag.UserName = _myCookieService.Unprotect(encryptedUserNameValue);

};

string? encryptedUserIdValue = Request.Cookies[CookieUserId];

if (encryptedUserIdValue != null)

ViewBag.UserId = Convert.ToInt32(_myCookieService.Unprotect(encryptedUserIdValue));

return View();

}
After making the above changes, run the application, and you should see that it works as
expected.
When Should We Use Encrypted Cookies in ASP.NET Core MVC?
Encrypted cookies in ASP.NET Core MVC are useful in scenarios where you need to store
sensitive information in cookies while ensuring that it remains secure and protected from
tampering or unauthorized access. The following are some of the specific scenarios when
you might want to use encrypted cookies:
 Storing Sensitive Information: Encrypting cookies adds an extra layer of
security if you need to store sensitive data, such as personal identifiers or
preferences, that should not be exposed or manipulated by the client.
363

 Preventing Tampering: Encrypted cookies help ensure that the data stored
within them cannot be tampered with. Since the cookie content is encrypted,
any modifications made by the client would invalidate the cookie.
 Enhancing Application Security: Encrypted cookies are part of a broader
approach to securing web applications. They should be used alongside other
security practices like HTTPS, secure cookie attributes, and proper session
management.

Persistent vs Non-Persistent Cookies in ASP.NET Core MVC


In this article, I will discuss the Differences Between Persistent and Non-Persistent
Cookies in ASP.NET Core MVC Applications. Please read our previous article
discussing How to Encrypt Cookies in ASP.NET Core MVC.
What are Persistent Cookies in ASP.NET Core MVC?
Persistent cookies in ASP.NET Core MVC are used to store data across multiple sessions
of a user. This can be useful for saving user preferences, authentication tokens, and other
information that needs to persist even after the browser is closed.
What are Non-Persistent Cookies in ASP.NET Core MVC?
A non-persistent cookie, also known as a session cookie, in ASP.NET Core MVC is a type
of cookie that is stored temporarily in the browser’s memory and is deleted when the
browser is closed. Unlike persistent cookies, which have a specific expiration date and
remain stored on the client’s device between sessions, non-persistent cookies exist only for
the duration of the user’s browsing session.
Differences Between Persistent vs Non-Persistent Cookies in ASP.NET
Core MVC
In ASP.NET Core MVC, the primary difference between persistent and non-persistent
cookies lies in their lifespan and how they are managed within a user’s session. Here’s a
detailed comparison:
Ad
1/2
00:25

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
Definition:
 Persistent Cookies: Persistent cookies, also known as permanent cookies,
remain stored on the client’s device even after the browser is closed. They
have an explicit expiration date set, and they persist until that date is reached
or until they are manually deleted.
 Non-Persistent Cookies: Non-persistent cookies, also known as session
cookies, are temporary and are deleted when the browser session ends. They
do not have a specific expiration date set.
Lifespan and Expiration
 Persistent Cookies: Have a specific expiration date set. Remain on the
user’s device after the browser is closed until it expires or is deleted.
364

 Non-Persistent Cookies (Session Cookies): These cookies have no


expiration date and are automatically deleted when the browser session
ends.
Implementation in ASP.NET Core MVC
Persistent Cookies are Implemented by setting the Expires property in the CookieOptions
object as follows.
var cookieOptions = new CookieOptions

Expires = DateTime.Now.AddDays(7) // Expires after 7 days

};

Response.Cookies.Append("PersistentCookie", "Value", cookieOptions);


Non-Persistent Cookies are Created without specifying an Expires property.
Response.Cookies.Append("SessionCookie", "Value");
Use Cases
 Persistent Cookies are suitable for long-term information like user
preferences, language settings, or authentication tokens. They help provide a
consistent user experience over multiple visits.
 Non-persistent cookies are ideal for data relevant to a single session, like
temporary form data. They enhance user experience without requiring long-
term data retention.
Security Considerations
 Persistent Cookies are at higher risk as they remain on the client’s device
longer, and they can expose data to unauthorized access if not properly
encrypted and secured.
 Non-persistent cookies are generally considered more secure due to their
temporary nature. They require secure handling, especially for sensitive
session data.
When Should We Use Persistent Cookies in ASP.NET Core MVC?
Persistent cookies, also known as permanent cookies, are stored on the user’s device for a
predetermined period or until they are explicitly deleted. They are used when you need to
remember information or preferences over multiple sessions:
 Remember Me Feature: When users log in to a website, a “Remember Me”
option is offered to keep them logged in for longer. This is convenient for
users who frequently return to your site.
 User Preferences: Storing user preferences like themes, language settings,
or other customizable aspects of your website. Persistent cookies ensure that
these preferences are remembered across different sessions.
 Shopping Carts in E-commerce: To remember the items in a user’s
shopping cart across different sessions if they haven’t completed the
checkout process.
When Should We Use Non-Persistent Cookies in ASP.NET Core MVC?
365

Non-persistent cookies, also known as session cookies, are deleted when the user closes
the browser. They are ideal for:
 Session Management: Maintaining user state within a single session, like
user authentication in a secure environment. The session ends once the user
closes the browser, and the cookie is discarded, which can be a security
benefit.
 Sensitive Data: Temporarily storing sensitive information that should not
persist beyond the current session. This reduces the risk of unauthorized
access to sensitive data.
 State Management for Single Visit: Used to store temporary information like
form data during a multi-step process within a single visit.

Sessions in ASP.NET Core MVC


In this article, I will discuss Sessions in ASP.NET Core MVC Applications with Examples.
Please read our previous article discussing Differences Between Persistent and Non-
Persistent Cookies in ASP.NET Core MVC Applications. Sessions are one of the several
ways to manage state in an ASP.NET Core application. The HTTP protocol is, by default,
stateless. But sometimes, it’s essential to hold user data while the user is browsing our
website. This data can help us remember the user actions. For example, on an e-commerce
website, this data can help to store products in a shopping cart.
What are Sessions in ASP.NET Core MVC?
A session is responsible for storing user data in the web server when browsing a web
application. In general, web applications operate on the stateless HTTP Protocol, where
each HTTP request is independent. The web server is unable to store anything on the
server related to the client’s request. That means the server lacks the ability to retain values
used in previous requests. To address this, ASP.NET Core provides the Session feature,
which enables the storage and retrieval of user data on the server side.
In ASP.NET Core MVC, sessions are a mechanism for storing and managing user-specific
data on the server side across multiple HTTP requests for a given session. Unlike cookies,
which are stored on the client side, session data is stored on the server side, enhancing
security and reducing the risk of exposing sensitive information.
How Does Session Work in Web Application?
The Session stores data in the Web Server Cache (To store the Session Data, the Server
uses a Temporary Caching Mechanism) by creating the SessionId as the key. Then, the
same SessionId is sent to the Client (Web Browser) using Cookies. That means the
SessionId is stored as a Cookie on the client machine, and these SessionId cookies are
sent with every request from client to server. For a better understanding, please have a look
at the following diagram.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:26 / 03:1710 Sec
366

Session work as Follows:


1. First, the Client (Web Browser) will send the HTTP Request to the Server.
2. Once the server receives the request, it will process it and generate the
response. Along with the generated response, it will create a SessionId and
then send that SessionId in the Response header using the Set-Cookie key.
At the same time, the SessionId will also be stored on the server cache as a
key with the required values. If this is not clear at the moment, don’t worry.
Once we see the examples, you will understand,
3. Once the client receives the response, from the next request onwards (as
long as the session has not expired), the client will send the HTTP Request
along with the cookie (cookie containing the SessionId) in the request header.
4. Once the server receives the request, it will check the Cookie, get the
SessionId from the Cookie, fetch the data from the server cache based on the
SessionId, and process the request accordingly.
Note: Each browser has a unique SessionId that is deleted at the end of the session and
not shared with other browsers. The default session timeout is 20 minutes but can be
configured based on business requirements.
Two types of sessions are available: In-Memory or In-Proc and Distributed or Out-Proc
Sessions. In our next article, we will discuss these two types of sessions, their differences,
and when to use one over another in detail with examples.
How To Use Session in ASP.NET Core MVC?
The package Microsoft.AspNetCore.Session offers a middleware component for
managing the Session in ASP.NET Core Web Application.
1. The Microsoft.AspNetCore.Session Package is included implicitly by the
Framework.
2. The Microsoft.AspNetCore.Session Package Provides Middleware
Component for Managing Session State.
To enable the Session Middleware Component in ASP.NET Core Web Application, the
Main method of the Program class must contain the following:
367

1. Any of the IDistributedCache Memory Cache Service. The


IDistributedCache implementation stores user data in the server by creating a
SessionId as the key. We will discuss Distributed Caching in detail in our
upcoming articles.
2. A call to the AddSession Service method where we need to configure the
session time out, whether the session is essential or not, and whether we
want to access the session using HTTP only or not.
3. A call to the UseSession Middleware component to register the session into
the application processing pipeline. It should be registered after the Routing
and before the MVC Middleware component. This is because we want the
session middleware component to be executed before executing the action
method.
The following code snippet shows how to set up the in-memory session provider with a
default in-memory implementation of IDistributedCache:

The order of middleware components is important. We must call the UseSession


Middleware Component after the UseRouting and before the MapControllerRoute
component in the ASP.NET Core MVC Web Application.
1. HttpContext.Session is only available after the session state is configured.
368

2. HttpContext.Session can’t be accessed before UseSession has been called.


Session Options:
In ASP.NET Core MVC, the SessionOptions class provides various properties to configure
session behavior. Each of these properties serves a specific purpose, enhancing the
functionality and security of your session management. Please have a look at the following
AddSessions service:

Let’s go through each of these properties:


IdleTimeout: The IdleTimeout indicates how long the session can be idle before its
contents are abandoned. The session will end if the user does not interact with the session
for this time period. Each session access resets the timeout.
Example: options.IdleTimeout = TimeSpan.FromMinutes(30); (sets the idle timeout to 30
minutes).
IOTimeout: Determines the maximum amount of time allowed for input/output operations to
complete when accessing the session state. Note this may only apply to asynchronous
operations. This timeout can be disabled using InfiniteTimeSpan.
Example: options.IOTimeout = TimeSpan.FromSeconds(10); (sets the IO timeout to 10
seconds).
Name: Sets the name of the session cookie. Example: options.Cookie.Name =
“.MySampleMVCWeb.Session”; (names the session cookie as
“.MySampleMVCWeb.Session”). By default, this cookie is named “.AspNetCore.Session”
HttpOnly: Indicates whether the cookie should be accessible only by the server. Setting
this to true enhances security by preventing client-side scripts from accessing the cookie.
Example: options.Cookie.HttpOnly = true; (makes the session cookie accessible only to
the server).
IsEssential: Marks the session cookie as essential for the application to function correctly.
This is important for compliance with certain privacy regulations like the EU’s GDPR, where
users can reject non-essential cookies. If true, then consent policy checks may be
bypassed. The default value is false. Example: options.Cookie.IsEssential = true; (marks
the session cookie as essential).
Path: Defines the path for which the cookie is valid. By default, it’s set to /, which means the
cookie is sent for all requests on the domain. Example: options.Cookie.Path =
“/Home”; (limits the cookie to the specified path).
369

SecurePolicy: Determines the policy for transmitting the cookie over secure channels like
HTTPS, and you can set the value of this property as follows:
 options.Cookie.SecurePolicy = CookieSecurePolicy.Always; (cookie sent
only over HTTPS).
 options.Cookie.SecurePolicy = CookieSecurePolicy.None; (cookie sent
over HTTP or HTTPS).
 options.Cookie.SecurePolicy =
CookieSecurePolicy.SameAsRequest; (cookie sent using the same
protocol as the request).
So, modify the Main method of the Program class as follows:
namespace SampleMVCWeb

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

//Configuring Session Services in ASP.NET Core

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>

options.IdleTimeout = TimeSpan.FromSeconds(10);

options.IOTimeout = TimeSpan.FromSeconds(10);

options.Cookie.Name = ".MySampleMVCWeb.Session";

options.Cookie.HttpOnly = true;

options.Cookie.IsEssential = true;

options.Cookie.Path = "/";
370

options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

});

var app = builder.Build();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

app.UseExceptionHandler("/Home/Error");

// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.

app.UseHsts();

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

//Configuring Session Middleware in ASP.NET Core

//It should and must be configured after UseRouting and before MapControllerRoute

app.UseSession();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

}
371

}
Important Note: If two requests simultaneously attempt to modify the contents of a session,
the last request overrides the first. When two requests seek to modify different session
values, the last request may override session changes made by the first.
Set and Get Session Values in ASP.NET Core MVC
In ASP.NET Core, the ISession interface provides methods to work with session data in a
key-value pair format. These methods allow us to get and set session values easily. These
methods are implemented as extension methods provided for common data types like string
and int. The following is the list of ISession extension methods:
 Get(ISession, String): Retrieves a value from the session as a byte array. If
the key does not exist, it returns null.
 GetInt32(ISession, String): This method tries to read an integer value from
the session. If the key is not found or the value cannot be converted to an int,
it returns null.
 GetString(ISession, String): This method retrieves a string value from the
session. If the key does not exist, it returns null.
 SetInt32(ISession, String, Int32): Sets an integer value in the session.
 SetString(ISession, String, String): Sets a string value in the session.
When using these methods, keep in mind:
 Session Start: The session must be started before you can set or get values.
This is typically done automatically in ASP.NET Core MVC applications.
 Serialization: The SetInt32 and SetString methods handle the serialization of
the given values into the session. On the other hand, GetInt32 and GetString
handle deserialization.
 Types: These methods are convenient for working with simple data types like
int and string. For more complex types, you would need to handle
serialization and deserialization manually.
 Lifetime: Session data is temporary and should not be used for long-term
storage. It’s typically cleared at the end of the session or when the browser is
closed.
Note: Don’t store sensitive data in the session state. The user might not close the browser
and clear the session cookie. Some browsers maintain valid session cookies across
browser windows. A session might not be restricted to a single user. The next user might
continue to browse the app with the same session cookie.
Modifying the Home Controller:
Next, modify the HomeController as follows. As you can see, we have created two constant
variables to store the session keys. We store user data in the session object within the
Index Action method. Further, if you notice, we are accessing the session data from the
About action method.
using Microsoft.AspNetCore.Mvc;

namespace SampleMVCWeb.Controllers

public class HomeController : Controller


372

const string SessionUserId = "_UserId";

const string SessionUserName = "_UserName";

public IActionResult Index()

//Let assume the User is logged in and we need to store the user information in the session

//Storing Data into Session using SetString and SetInt32 method

HttpContext.Session.SetString(SessionUserName, "pranaya@dotnettutotials.net");

HttpContext.Session.SetInt32(SessionUserId, 1234567);

return View();

public string About()

//Accessing the Session Data inside a Method

string? UserName = HttpContext.Session.GetString(SessionUserName);

int? UserId = HttpContext.Session.GetInt32(SessionUserId);

string Message = $"UserName: {UserName}, UserId: {UserId}";

return Message;

public IActionResult Privacy()

return View();

}
373

}
Note: After 10 seconds, the cookie expires, and you will not get the data once the cookie
expires.
How do you Access the Session Object in a View?
To use the Session inside a View, we need to inject the IHttpContextAccessor object into
our view, and then we need to use the IHttpContextAccessor object to get the HttpContext
and Session. For this, we need to add the following two statements in our view. The first
statement adds the namespace and the second statement injects the
IHttpContextAccessor, and using this HttpContextAccessor; we can access the HttpContext
and Session object.
@using Microsoft.AspNetCore.Http;
@inject IHttpContextAccessor HttpContextAccessor
Modifying the Index.cshtml view:
@using Microsoft.AspNetCore.Http;

@inject IHttpContextAccessor HttpContextAccessor

@{

ViewData["Title"] = "Home Page";

<div class="text-left">

<b>User Name:</b>
@HttpContextAccessor?.HttpContext?.Session.GetString("_UserName")

<br />

<b>User Id:</b> @HttpContextAccessor?.HttpContext?.Session.GetInt32("_UserId")

</div>
Modifying the Privacy.cshtml view:
@using Microsoft.AspNetCore.Http;

@inject IHttpContextAccessor HttpContextAccessor

@{

ViewData["Title"] = "Privacy Policy";

}
374

<h1>@ViewData["Title"]</h1>

<div class="text-left">

<b>User Name:</b>
@HttpContextAccessor?.HttpContext?.Session.GetString("_UserName")

<br />

<b>User Id:</b> @HttpContextAccessor?.HttpContext?.Session.GetInt32("_UserId")

</div>
You will get the following exception if you run the application with the above changes. This
is because we use the HttpContextAccessor service but have not yet configured this service
into the built-in IOC container.

Now, we need to configure the HttpContextAccessor service within the Main method of the
Program class. The following piece of code does the same.
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So, modify the Main method of the Program class as follows:
namespace SampleMVCWeb

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

//Configuring Session Services in ASP.NET Core


375

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>

options.IdleTimeout = TimeSpan.FromSeconds(10);

options.IOTimeout = TimeSpan.FromSeconds(10);

options.Cookie.Name = ".MySampleMVCWeb.Session";

options.Cookie.HttpOnly = true;

options.Cookie.IsEssential = true;

options.Cookie.Path = "/";

options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

});

builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

app.UseExceptionHandler("/Home/Error");

// The default HSTS value is 30 days. You may want to change this for production scenarios,
see https://aka.ms/aspnetcore-hsts.

app.UseHsts();

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();
376

app.UseAuthorization();

//Configuring Session Middleware in ASP.NET Core

//It should and must be configured after UseRouting and before MapControllerRoute

app.UseSession();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

}
Now, run the application, and by default, it will execute the Index Action Method of the
Home Controller and set the session values. Then, it will render the Index view where you
can see the session data, as shown in the image below.

When it returns the response to the client (Web Browser), the Web Server also includes the
generated SessionId in the response header using the Set-Cookie. To confirm this, open a
different web browser, open the developer tool, and then issue a request to the Index action
method. Then, go to the Network tab, inspect the request, and go to the Response header.
You will see the Set-Cookie property where the web server sends the SessionId and its
unique value. This SessionId value is going to be unique per session.
377

From the next HTTP Request onwards, the Client (Browser) will send the SessionId in the
request header using Cookie. Within 10 seconds, if you visit the Privacy page, it will also
render the session data, as shown in the image below.

If you check the Network tab and inspect the request, you will see that one cookie is being
sent from the client to the web server using the Request header, as shown in the image
below. This happens in each and every request from the client to the server as long as the
session has not expired.
378

Again, within 10 seconds, if you visit the About page, it will also render the session data, as
shown in the image below.

Now, if you visit the Privacy page (Not the Index page where we store the session data)
after 10 seconds, you will not get the session data as shown in the image below.

In this case, it also sends the cookie in the HTTP request header, which is expired. If you
want, access the Index Action method, reset the Session timeout, and then the same
SessionId will be used.
379

Advantages and Disadvantages of using Sessions in ASP.NET


Core MVC
Sessions have advantages and disadvantages that you should consider when deciding
whether to use sessions in your application.
Advantages of Using Sessions in ASP.NET Core MVC:
1. User State Management: Sessions allow us to manage and maintain user-
specific data throughout their interactions with your application. This is useful
for scenarios like maintaining user authentication status, shopping carts, and
user preferences.
2. Server-Side Storage: Session data is stored on the server, reducing the risk
of exposing sensitive information to clients or tampering.
3. Flexibility: Sessions can store various types of data, including complex
objects and custom data, providing flexibility in the types of information you
can manage.
4. Large Data: Sessions are not as limited in size as cookies, allowing you to
store more data.
5. Centralized Management: ASP.NET Core provides built-in session
management mechanisms, including configuration options and different
session storage providers.
6. Scalability: ASP.NET Core supports various session storage options, such
as in-memory storage, distributed caches, and external databases, allowing
you to scale your application as needed.
7. Security: Session data is less susceptible to tampering or attacks than client-
side storage options like cookies.
Disadvantages of Using Sessions in ASP.NET Core MVC:
1. Server Resources: Storing session data on the server consumes server
memory and resources, which might impact the performance of your
application.
2. Persistence: Session data is typically stored in memory, which means it will
lost if we restart the server or in case of server failures.
3. Complexity: Implementing and managing sessions can introduce complexity
to your application architecture, particularly when using distributed session
storage.
4. Data Serialization: Session data often needs to be serialized and
deserialized, which can impact performance, especially for complex objects.
When should Sessions be used in ASP.NET Core MVC?
Here are some situations where using sessions is appropriate:
1. User Authentication and Authorization: Sessions are commonly used to
manage user authentication and authorization status. You can store
information about the user’s identity and roles in a session to maintain their
logged-in state.
2. User Preferences: Sessions can store and manage user preferences, such
as language settings, theme choices, or display preferences. This allows you
to provide a customized experience for each user.
380

3. Shopping Carts: Sessions are often employed to manage shopping cart


data. You can store information about items added to the cart and their
quantities, ensuring that the cart contents are retained between page views
and interactions.
4. Form Data Persistence: Sessions can temporarily store form data between
multiple steps of a multi-page process. This is useful for scenarios like multi-
step forms or wizards.
5. Temporary Data: Sessions can store temporary data that needs to persist for
a short period, such as alerts, notifications, or messages.
6. User-Specific Data: When you need to maintain user-specific data that
doesn’t belong to a database, sessions provide a convenient way to store and
access this information.
7. Cross-Request Communication: Sessions allow you to share data between
different requests from the same user, enabling interactions between different
parts of your application.
8. Caching: Sessions can be used for caching frequently accessed data to
improve performance and reduce the need for repeated data retrieval.
9. Single Sign-On (SSO): Sessions can be used for implementing single sign-
on across multiple applications, allowing users to authenticate once and
access multiple services without repeated logins.

In-Memory or In-Proc vs. Distributed or Out-Proc Sessions in ASP.NET Core


MVC
In this article, I will discuss In-Memory or In-Proc vs Distributed or Out-Proc Sessions in
ASP.NET Core MVC Applications with Examples. Please read our previous article
discussing Sessions in ASP.NET Core MVC Application.
In-Memory or In-Proc vs. Distributed or Out-Proc Sessions in ASP.NET
Core MVC
In ASP.NET Core MVC, managing session state is an important aspect of web application
development. The choice between In-Memory (In-Proc) and Distributed (Out-Proc) sessions
impacts the application’s scalability, performance, and reliability.
In-Memory (In-Proc) Sessions in ASP.NET Core MVC
 Storage: Session data is stored in the memory of the web server.
 Performance: Generally faster since the data is stored in the same process
as the web application.
 Scalability: Limited scalability. It is not suitable for web applications that
require load balancing across multiple servers because session data is tied to
a specific server.
 Reliability: All session data is lost if the web server goes down.
 Use Case: Ideal for small-scale applications with a single server and less
critical session data.
Distributed (Out-Proc) Sessions in ASP.NET Core MVC
 Storage: Session data is stored outside the web server, typically in a
distributed cache or database like Redis, SQL Server, etc.
381

 Performance: This can be slower than in-memory sessions due to network


latency and the time required to serialize/deserialize session data.
 Scalability: Highly scalable. Suitable for applications deployed in a load-
balanced environment across multiple servers.
 Reliability: More reliable as session data is not lost if a single server goes
down.
 Use Case: Ideal for large-scale applications that require high availability and
are deployed in a distributed environment.
Using In-Memory (In-Proc) and Distributed (Out-Proc) sessions in ASP.NET Core MVC
involves different configurations and considerations. Let us proceed and understand how to
Implement In-Memory (In-Proc) and Distributed (Out-Proc) sessions in the ASP.NET Core
MVC Application.
Implementing In-Memory (In-Proc) Sessions in ASP.NET Core
MVC:
The In-Memory or In-Proc session state is stored in the web server’s memory. It’s fast but
unsuitable for web applications running on multiple servers (like in a web farm) since the
session state is local to a server.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
Implementing in-memory or in-proc (in-process) sessions in ASP.NET Core MVC involves a
few key steps. The in-memory session storage is suitable for single-server scenarios where
you don’t need to persist data across server restarts.
Configure Services for Session:
In your Program.cs file (since ASP.NET Core 6 no longer uses Startup.cs by default),
configure the session services. This includes adding a distributed memory cache and
session services.
// Add Session services to the container.

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>

options.IdleTimeout = TimeSpan.FromMinutes(30); // e.g. 30 minutes session timeout

options.Cookie.HttpOnly = true;

options.Cookie.IsEssential = true;

});
Use Session Middleware
In the same Program.cs file, ensure the session middleware is added to the application’s
request pipeline. This should be done before routing and endpoints middleware.
382

app.UseRouting();

app.UseSession(); // Add this line to use session

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");
Using Sessions in Controllers
In your controllers, you can access the session via the HttpContext.
using Microsoft.AspNetCore.Mvc;

namespace SampleMVCWeb.Controllers

public class HomeController : Controller

public IActionResult Index()

// Set a value in session

HttpContext.Session.SetString("MySessionKey", "MySessionValue");

return View();

public IActionResult AnotherAction()

// Retrieve the value from session

var value = HttpContext.Session.GetString("MySessionKey");

ViewBag.SessionValue = value;

return View();

}
383

}
Implementing Distributed (Out-Proc) Sessions in ASP.NET Core
MVC:
Distributed sessions are suitable for applications running on multiple servers. The session
state is stored in an external storage like Redis or SQL Server.
Implementing distributed (out-of-process) sessions in ASP.NET Core MVC to store session
data in SQL Server involves several steps. This approach is useful when you have a web
farm scenario (multiple servers hosting your application), and you want to ensure session
data is consistent across servers.
Add Required Packages
First, add the necessary NuGet packages to your project:
 Microsoft.EntityFrameworkCore.SqlServer for Entity Framework Core with
SQL Server.
 Microsoft.Extensions.Caching.SqlServer for SQL Server distributed
caching.
Configure SQL Server for Session Storage
First, create a database in the SQL Server database with the SessionDB, where we will
store the session data.
Before proceeding, ensure you have a SQL Server database available. You’ll need to
create a table to store session data. The dotnet sql-cache command is part of the .NET
Core CLI, specifically designed for working with distributed SQL server cache. If you’re
unable to find this command, it’s likely that the necessary tool is not installed on your
system. To install it, you should run the following command:
dotnet tool install –global dotnet-sql-cache
The sql-cache create tool can be used to create this table. Run the following command in
your terminal, replacing the placeholders with your database details. The following
command creates a table named MySessions in the specified database to store session
data.
dotnet sql-cache create “Data Source=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Initial Catalog=SessionDB;Integrated
Security=True;TrustServerCertificate=True” dbo MySessions
Once the above command is executed successfully, the MySessions table must be created
in the SessionDB database with the following structure:
384

Understanding the Table Structure:


 ID: This is a unique identifier for each session. It’s typically a string that
represents the session key. This key is used by the ASP.NET Core application
to identify and retrieve the session data for each request.
 Value: The Value column holds the serialized session data. When your
application stores any data in the session (using
HttpContext.Session.SetString, SetInt32, Set, etc.), ASP.NET Core serializes
this data into a binary format and stores it in this column.
 ExpiresAtTime: This column stores the timestamp at which the session will
expire. The session state middleware in ASP.NET Core checks this value to
determine if the session data is still valid. If the current time is greater than
the ExpiresAtTime, the session is considered expired, and the middleware will
not return the stored data.
 SlidingExpirationInSeconds: This column is used when the session is
configured for sliding expiration. Sliding expiration resets the session’s
expiration time to be a set amount of time from the current time, but only if the
session is accessed. This value represents the amount of time (in seconds) to
extend the session’s life from the current moment when a request is made. If
this column is used, each time the session is accessed, the ExpiresAtTime is
updated to be the current time plus the value in SlidingExpirationInSeconds.
 AbsoluteExpiration: This column is used for an absolute expiration policy,
where the session will expire at a specific point in time, regardless of whether
it’s accessed. It’s an alternative to sliding expiration. This would be set to a
specific point in time (timestamp), after which the session should no longer be
considered valid.
Configuring Connection String:
Storing the Connection string in the AppSettings.json file. So, modify the AppSettings.json
file as follows. Here, you can see we have added the ConnectionStrings section.
{
385

"Logging": {

"LogLevel": {

"Default": "Information",

"Microsoft.AspNetCore": "Warning"

},

"AllowedHosts": "*",

"ConnectionStrings": {

"EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=SessionDB;Trusted_Connection=True;TrustServerCertific
ate=True;"

}
Step 4: Configure DbContext
Create a DbContext class for the application. So, add a class
named EFCoreDBContext.cs within the Models folder and then copy and paste the
following code.
using Microsoft.EntityFrameworkCore;

namespace SampleMVCWeb.Models

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)

}
386

}
Configure Services for Distributed Cache
In your Program.cs file, configure Entity Framework Core, and the distributed SQL Server
cache. In this configuration, replace EFCoreDbContext and EFCoreDBConnection with your
specific Entity Framework Core context and connection string.
// Add Session Services to the Container.

builder.Services.AddDbContext<EFCoreDbContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"))
);

//Add Distributed Sql Server Cache Service

builder.Services.AddDistributedSqlServerCache(options =>

//Specify the ConnectionString, Database Schema and TableName

options.ConnectionString =
builder.Configuration.GetConnectionString("EFCoreDBConnection");

options.SchemaName = "dbo";

options.TableName = "MySessions";

});

//Add AddSession Service to enable Session Feature

//Adds services required for application session state.

builder.Services.AddSession(options =>

options.IdleTimeout = TimeSpan.FromMinutes(30);

options.Cookie.HttpOnly = true;

options.Cookie.IsEssential = true;
387

});
Note: The AddDistributedSqlServerCache method in ASP.NET Core is used to set up a
distributed cache that is backed by a SQL Server database. This method is part of the
Microsoft.Extensions.DependencyInjection namespace. Distributed caching is a method of
caching where the data is stored across multiple servers, which can help in maintaining
consistency and performance in web applications, especially those deployed in a load-
balanced, multi-server environment.
Configure Session Middleware
In the same Program.cs file, add the session middleware to the application’s request
pipeline.
app.UseRouting();

app.UseAuthorization();

//Configuring Session Middleware in ASP.NET Core

//It should and must be configured after UseRouting and before MapControllerRoute

app.UseSession();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");
Using Sessions in Controllers
You can now use sessions in your controllers just like you would with in-memory sessions:
using Microsoft.AspNetCore.Mvc;

namespace SampleMVCWeb.Controllers

public class HomeController : Controller

public IActionResult Index()

HttpContext.Session.SetInt32("UserId", 123456);

HttpContext.Session.SetString("UserName", "info@dotnettutorials.net");
388

return View();

public IActionResult Privacy()

var sessionUserName = HttpContext.Session.GetString("UserName");

ViewBag.UserName = sessionUserName;

var sessionUserId = HttpContext.Session.GetInt32("UserId");

ViewBag.UserId = sessionUserId;

return View();

}
Next, modify the Index.cshtml view as follows:
@{

ViewData["Title"] = "Home Page";

<div class="text-left">

<h2>Index Page</h2>

</div>
Next, modify the Privacy.cshtml view as follows:
@{

ViewData["Title"] = "Privacy Policy";

<h1>@ViewData["Title"]</h1>

<div class="text-left">
389

<b>User Name:</b>@ViewBag.UserName

<br />

<b>User Id:</b> @ViewBag.UserId

</div>
Now, run the application and visit the Index action method where we are saving the session
data. Now, once you access the Home Page, go to the database and check the MySession
data, and you should see the following:

Note: The AbsoluteExpiration column is not used by the default SQL Server distributed
cache implementation in ASP.NET Core. This column is part of the schema for compatibility
with other potential caching implementations that might use absolute expiration. However,
in the default setup with ASP.NET Core and SQL Server, this column is left null and does
not play a role in the expiration mechanism.
How do you delete the distributed session data from the SQL Server
database?
Managing and deleting expired session data from the SQL Server database in an ASP.NET
Core MVC application, especially when using distributed sessions, is important for
maintaining performance and efficient data management.
SQL Server doesn’t automatically clean up expired sessions. Therefore, you need to
implement a mechanism to remove old session data periodically. You can create an SQL
Server Agent Job or a scheduled task that runs an SQL script to delete expired sessions.
Here is an example SQL script that you could run:
DELETE FROM [YourSessionTable] WHERE ExpiresAt < GETUTCDATE();
Replace [YourSessionTable] with the name of your session table. This script deletes all
sessions that have expired according to the ExpiresAt column.
Key Differences Between In-Proc and Out-Proc Sessions:
 Location of Data: In-Proc stores data in server memory, and Out-Proc stores
data in a separate system.
 Data Persistence: Out-Proc can maintain data persistence across server
restarts, whereas In-Proc cannot.
 Resource Utilization: In-Proc uses server memory, which can be a
constraint. Out-Proc uses external resources, which adds to network
overhead but frees up server memory.
 Complexity: Implementing and maintaining a distributed session state is
more complex than in-memory sessions.
390

Choosing the Right Approach


 Size and Scale of the Application: Small, single-server applications might
benefit from the simplicity of In-Proc sessions. Large, distributed applications
usually require Out-Proc sessions.
 Session Data Criticality: Out-Proc is a better choice if losing session data is
unacceptable.
 Load Balancing Needs: If the application is load-balanced across multiple
servers, Out-Proc sessions are necessary to ensure session data is available
across all servers.
 Performance Considerations: In-Proc can offer better performance but at
the cost of scalability and reliability.

Differences Between Cookies and Sessions in ASP.NET Core MVC


In this article, I will discuss the Differences Between Cookies and Sessions in ASP.NET
Core MVC Applications with Examples. Please read our previous article discussing In-
Memory or In-Proc vs Distributed or Out-Proc Sessions in ASP.NET Core
MVC Application.
Differences Between Cookies and Sessions in ASP.NET Core MVC
Cookies and Sessions are mechanisms for State Management in ASP.NET Core MVC
Applications, but they have distinct differences in where data is stored, how long it persists,
and how it’s accessed. Here’s a comparison of cookies and sessions in the ASP.NET Core
MVC Web Application:
Storage Location
 Cookies: Cookies are stored on the client’s browser. They are sent along with
every HTTP request to the server, which can increase the load on bandwidth
if the cookies are large or numerous.
 Sessions: Session data is stored on the server. The server sends a session
identifier to the client, usually in a cookie, which is used to fetch session data
on subsequent requests. The bulk of the data remains on the server, which
can be more secure.
Lifespan
 Cookies: Cookies can be persistent or session-based. Persistent cookies
remain on the client’s device until their set expiration date, while session
cookies are deleted when the browser is closed.
 Sessions: Session data is usually temporary and is cleared after a set timeout
or when the user ends their session (typically by closing the browser or
logging out).
Security
 Cookies: Because they are stored on the client-side, cookies are more
vulnerable to security risks such as Cross-Site Scripting (XSS) and Cross-
Site Request Forgery (CSRF). Secure and HttpOnly flags can be used to
enhance security.
 Sessions: Generally considered more secure as the data is stored on the
server. The session identifier needs to be protected to prevent session
hijacking.
Data Capacity
391

 Cookies: Limited in size (typically 4KB per cookie). Not suitable for storing
large amounts of data.
 Sessions: Can handle larger amounts of data as they are stored on the
server. The limit depends on the server’s memory and configuration.
Use Cases
 Cookies: Cookies are commonly used to store user preferences,
authentication tokens, and other small pieces of data that need to persist
across requests.
 Sessions: Sessions are used to store data such as user profiles, shopping
cart contents, or any other data that needs to persist across multiple requests
but is too large or sensitive to store in a cookie.
Comparison Between Cookies and Sessions
 Storage Location: Cookies are stored on the client side, while sessions use
server-side storage.
 Data Security: Sessions are generally more secure as the data is stored on
the server.
 Capacity: Sessions can handle more data as they are not limited by browser
restrictions on cookie size.
 Persistence: Cookies can persist across browser sessions if configured, while
session data is typically tied to a single browser session.
 Scalability: Cookies are more scalable as they do not put the load on the
server for data storage. Sessions can be more challenging to scale in load-
balanced environments unless a distributed session management system is
used.
When to Use Each
 Use cookies for small pieces of data that need to persist across browser
sessions and are not sensitive.
 Use sessions for larger or sensitive data that should not be stored on the
client side and for data that only needs to persist during a single browser
session.
Considerations for Choosing
 Data Sensitivity: Use sessions for sensitive data to avoid client-side
tampering.
 Data Size: For large data, prefer sessions. Cookies are suitable for small bits
of data.
 Persistence Need: If you need data to persist beyond a user session, cookies
are a better option.
 Performance Impact: Cookies are sent with every HTTP request, which can
impact performance, especially when dealing with large cookies or many
small cookies.
 Scalability Requirements: For applications that scale horizontally across
multiple servers, managing sessions can be more complex.
 Statelessness: In a truly RESTful application, statelessness is key, and
sessions (which are stateful) might be avoided in favor of stateless
authentication mechanisms like tokens in cookies.
392

Filters in ASP.NET Core MVC


In this article, I will discuss Filters in ASP.NET Core MVC Applications with Examples.
Filters are one of the most important concepts in ASP.NET Core MVC Applications, and as
developers, we should be aware of this concept. So, from this and in a few upcoming
articles, I will discuss ASP.NET Core MVC Filters in Detail. As part of this article, we will
discuss the following pointers.
1. What are Filters in ASP.NET Core MVC?
2. Why are Filters in ASP.NET Core MVC?
3. What are the Differences Between Filters and Middlewares in ASP.NET
Core?
4. Types of Filters in ASP.NET Core MVC
5. Advantages and Disadvantages of Filters in ASP.NET Core MVC
6. Where can we configure filters in ASP.NET Core MVC?
7. Default Filters Execution Order in ASP.NET Core MVC
What are Filters in ASP.NET Core MVC?
Filters are used to add cross-cutting concerns, such as Logging, Authentication,
Authorization, Exception Handling, Caching, etc., to our application. In ASP.NET Core
Applications, Filters allow us to execute cross-cutting logic in the following ways:
1. Before an HTTP request is handled by a controller action method.
2. After an HTTP request is handled by a controller action method.
3. After the response is generated but before it is sent to the client.
We have already discussed in Routing that when a client makes a request, that request
comes to the Routing Engine, and then the Routing Engine navigates that Request to the
Controller Action Method. So, the Controller action method will handle the incoming request
and send the response back to the client who initially made the request, as shown in the
image below.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec
393

But what will you do if you want to execute some code or logic before or after the action
method is executed, as shown in the image below?

If that is your requirement, then you need to use Filters in your ASP.NET Core application.
Filters in ASP.NET Core Applications are the Attributes that allow us to inject logic or code
before or after an action method is executed.
Why are Filters in ASP.NET Core MVC?
In ASP.NET Core MVC, Filters are used to Perform cross-cutting concerns. They are as
follows:
 Caching
 Logging
 Error Handling
 Modifying the Result
 Authentication and Authorization, etc.
What are the Differences Between Filters and Middlewares in ASP.NET
Core?
Before proceeding further, we need to understand the differences between Filters and
Middleware Components in ASP.NET Core applications. The following are the Key
differences between Filters and Middleware Components:
Scope of Application (Where they Applied):
 Filters: Filters are primarily applied to individual controller actions or
controllers. They are used to add specific behaviors or concerns to
processing a single action or a group of actions within a controller.
 Middlewares: Middlewares are applied to the entire application’s request
processing pipeline. They can handle requests and responses globally,
regardless of the specific controller or action being invoked.
Execution Point (When they Execute):
 Filters: Filters execute within the ASP.NET Core Framework’s pipeline and
are part of the controller/action execution process. They are triggered before
or after the execution of a specific action method.
 Middlewares: Middlewares execute earlier in the Request Processing
Pipeline, typically before the request reaches the ASP.NET Core MVC
Controller action method. They can intercept requests and responses and
perform tasks at various stages of the request processing pipeline, such as
Routing, Authentication, Response Formatting, etc.
Purpose and Concerns (Why they are Used):
394

 Filters: Filters are designed to handle concerns specific to the ASP.NET Core
MVC framework, such as Logging, Authentication, Authorization, Exception
Handling, Caching, Custom Logic, etc.
 Middlewares: Middlewares are more general-purpose and can handle many
concerns, including Routing, Authentication, Request/Response Logging,
Compression, Security, etc.
Configuration (How they Configured):
 Filters: Filters are typically configured using attributes (e.g., [Authorize],
[AllowAnonymous], etc.) on controllers or action methods. You can also
register global filters in the Program Class.
 Middlewares: Middlewares are configured and ordered in the Program class
(e.g., UseHttpsRedirection(), UseAuthorization(), MapControllers(), etc).
Execution Order (What is the Execution Order):
 Filters: The execution order for filters is decided based on the type of filters
you are applying to the controllers and action methods. So, the order of
Filters is not important.
 Middlewares: The execution order for middleware components is determined
by the order in which they are added to the IApplicationBuilder pipeline, i.e.,
to the Request Processing Pipeline. So, the order of Middleware Components
is important.
So, Filters and Middleware Components serve different purposes and have different scopes
within an ASP.NET Core MVC application. Filters are more tightly integrated with the
ASP.NET Core MVC framework and are applied at the controller/action level. On the other
hand, Middleware Components are applied globally to the entire application.
Types of Filters in ASP.NET Core MVC
In ASP.NET Core MVC, there are several types of filters available that you can use to
modify the behavior in the request and response processing pipeline. The following are the
commonly used Filters in ASP.NET Core MVC Applications:
Authorization Filters in ASP.NET Core:
The Authorization Filter is used to perform Authentication and Authorization checks before
an action method is executed. Examples include AuthorizeAttribute for role-based or
policy-based authorization and AllowAnonymousAttribute to allow unauthenticated users
to access an action.
 [Authorize]: This Built-in Filter restricts access to actions or controllers for
UnAuthenticated Users. You can also specify Roles, Policies, or Claims
based on which it can also decide who can access specific resources.
 [AllowAnonymous]: This Built-in Filter allows unauthenticated users to
access actions or controllers.
 Custom Authentication: You can also create Custom Authentication. To do
so, we need to create a class implementing the IAuthorizationFilter interface
and provide implementations for the OnAuthorization method, where we
need to write the custom authentication logic according to our business
requirements.
Action Filters in ASP.NET Core:
The Action Filters in the ASP.NET Core MVC Application are executed before and after an
action method is executed. They perform tasks like Logging, Modifying the Action’s
Arguments, or Altering the Action’s Result.
395

In ASP.NET Core, you can create a Custom Action Filter in two ways: First, by creating a
class implementing the IActionFilter interface and providing implementations
for [OnActionExecuting] and [OnActionExecuted] methods. Second, by creating a class
inherited from the ActionFilterAttribute class and overriding the [OnActionExecuting]
and [OnActionExecuted] methods.
[OnActionExecuting] and [OnActionExecuted]: These are the two methods within a
Custom Action Filter to execute logic before and after an action method is called. The
OnActionExecuting method executes before the action method is invoked, and the
OnActionExecuted method executes after the action method is invoked.
How Do We Apply the Custom Filter to Controllers and Action Methods in ASP.NET
Core?
If the custom class is created by overriding the ActionFilterAttribute class, then it is an
Attribute, and we can directly apply that Custom Filter to the Action methods or Controllers.
However, you cannot apply Custom Action Filters (if the custom class is created by
implementing the ActionFilter interface) directly to the controllers and action methods. For
this, we need to use TypeFilter and ServiceFilter as built-in attributes.
TypeFilter and ServiceFilter are built-in attributes in ASP.NET Core that apply Custom
Action Filters as Attributes to controller actions or controllers. Later, as we progress, we will
discuss the differences between TypeFilter and ServiceFilter Attributes and how to use
them.
Result Filters in ASP.NET Core:
The Result Filters in ASP.NET Core MVC Application runs after the action method has been
executed but before the result is processed and sent to the client. This means you can
modify the view or the result data before it gets rendered to the output stream. They are
used for tasks such as Adding Headers to the response, Modifying the Result, etc.
In ASP.NET Core, you can create a Custom Result Filter in two ways: First, by creating a
class implementing the IResultFilter interface and providing implementations
for [OnResultExecuting] and [OnResultExecuted] methods. Second, by creating a class
inherited from the ResultFilterAttribute class and overriding the [OnResultExecuted] and
[OnResultExecuted] methods.
[OnResultExecuting] and [OnResultExecuted]: These are the two methods within a
Custom Result Filter to execute logic before and after the Result is generated.
The OnResultExecuting method executes before the result is generated, and
the OnResultExecuted method executes after the result is generated.
Exception Filters in ASP.NET Core:
The Exception Filters are executed when an unhandled exception occurs during the
execution of an action method. They are used for Logging, Error Handling, and Displaying
Different Error Pages Based on the Error Status Codes.
In ASP.NET Core, you can create a Custom Exception Filter in two ways: First, you can
create a class implementing the IExceptionFilter interface and provide implementations for
the [OnException] method. Second, by creating a class inherited from
the ExceptionFilterAttribute class and overriding the [OnException] method.
Where Can We Configure Filters in ASP.NET Core MVC?
In the ASP.NET Core MVC Application, Filters can be configured in the following three
places:
Configuring Filters at Controller Level:
396

We can apply filters at the controller level by decorating the controller with the Filter
attribute, as shown in the below code. When we apply the Filter at the controller level, it will
apply to all the actions of that controller.
[Authorize] // Authorization filter applied at the controller level

public class HomeController : Controller

public IActionResult Index()

// Action logic

}
Configuring Filters at Globally:
We can configure filters globally in the Program class. By adding filters as services, we can
ensure they are applied globally to all controllers and actions in our application. Following is
an example of configuring a global filter in the Program.cs class:
builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new MyGlobalFilter());

});
Action Method Level:
We can also apply filters directly to individual action methods within our controller by using
the Filter Attribute, as shown in the below code. This allows us to apply specific filters only
to specific action methods.
public class MyController : Controller

[MyCustomFilter] // Custom filter applied to this action

public IActionResult MyAction()

// Action logic
397

}
Default Filters Execution Order in ASP.NET Core MVC
In ASP.NET Core MVC, filters are executed in a specific order known as the “Default
Execution Order“. The default execution order ensures filters are applied properly
throughout the request processing pipeline. The default execution order, from the earliest to
the latest in the pipeline, is as follows:
 Authorization Filters: Authorization filters are executed first. They are
responsible for checking whether the current user can access the requested
resource or action. If authorization fails, the request will be short-circuited,
and the action method will not be executed.
 Action Filters (Before Action Execution): Action filters with “Before Action
Execution” logic are executed before the action method is invoked. These
filters can perform tasks like logging, input validation, or pre-processing data.
 Model Binding: Model binding occurs at this stage. It binds incoming data to
action method parameters and executes model validation.
 Action Execution: The action method itself is executed.
 Action Filters (After Action Execution): Action filters with “After Action
Execution” logic are executed after the action method completes its
execution. These filters can perform tasks like logging, post-processing data,
etc.
 Result Filters (Before Result Execution): Result filters with “Before Result
Execution” logic are executed before the action result is executed. These
filters can modify the result or perform additional processing.
 Action Result Execution: The action result, which can be a view or any
other result type, is executed.
 Result Filters (After Result Execution): Result filters with “After Result
Execution” logic are executed after the action result has been executed.
These filters can perform tasks like logging or post-processing of the result.
 Exception Filters (If an Exception Occurs): Exception filters are executed if
an unhandled exception occurs during the request’s processing. These filters
can handle the exception, log it, and return an appropriate error response.

Exception Filter in ASP.NET Core MVC


In this article, I will discuss the Exception Filter in ASP.NET Core MVC Application with
Examples. Please read our previous article discussing the basic concepts of Filters in
ASP.NET Core MVC Applications. As part of this article, we will discuss the following
pointers in detail.
1. What is an Exception Filter in ASP.NET Core MVC?
2. Built-in Exception Filter in ASP.NET Core MVC
3. Example to understand UseExceptionHandler Middleware
4. How Do We Create a Custom Exception Filter in ASP.NET Core MVC?
398

5. Differences Between Exception Handler and Exception Filter in ASP.NET


Core.
What is an Exception Filter in ASP.NET Core MVC?
In ASP.NET Core MVC, Exception Filter allows us to handle unhandled exceptions that
occur while processing an HTTP request within our application. Exception Filters in
ASP.NET Core Applications are used for tasks such as Logging Exceptions, Returning
Custom Error Responses, and Performing any other necessary action when an exception
occurs.
Exception Filter also provides a way to centralize the exception-handling logic and keep it
separate from our controller or from the business and data access logic, making our code
more organized and maintainable.
Built-In Exception Filter in ASP.NET Core MVC
The ASP.NET Core Framework does not provide any specialized Built-in Exception Filter
for Global Error Handling. As developers, we generally use the UseExceptionHandler
middleware component to handle exceptions globally.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
However, we can also create a Custom Exception Filter to handle unhandled exceptions
globally. In ASP.NET Core, we can create the Custom Exception Filter in two ways: First,
we can create a class implementing the IExceptionFilter interface and
provide implementations for the [OnException] method. Second, by creating a class
inherited from the ExceptionFilterAttribute class and overriding
the [OnException] method.
Example to understand UseExceptionHandler Middleware in ASP.NET
Core
Let us first see an example of how the UseExceptionHandler middleware component
handles unhandled exceptions globally. Then, we will see how to create a Custom
Exception Filter.
The UseExceptionHandler Middleware allows us to specify an error-handling path that will
be executed when an unhandled exception is thrown within our application. We can
configure this using the Main method of our Program class.
To configure UseExceptionHandler middleware for global Exception Handling, please add
the following code to the program class. With the following code in place, if an exception
occurs and if the environment is not Development, then the user will be redirected to the
“/Home/Error” URL. Here, Home is the Controller name, and Error is the action method
within the Home Controller. If the environment is Development, it will display the complete
error details using the UseDeveloperExceptionPage middleware component.
if (app.Environment.IsDevelopment())

app.UseDeveloperExceptionPage();

}
399

else

// This will handle exceptions and redirect to the specified error page.

app.UseExceptionHandler("/Home/Error");

}
Modify the Home Controller:
Next, modify the Home Controller as follows. As you can see in the code below, within the
Index action method, we have written the logic in a way that it will throw runtime exceptions
while executing it. Further, if you notice, we have not handled that exception within the
Index action method. Again, we have added the Error action method, which should be
executed when there is an unhandled exception and if the environment is not set to be
Development.
using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

public ActionResult Index()

int x = 10;

int y = 0;

int z = x / y;

return View();

public ActionResult Error()

return View();
400

}
Next, add the Error.cshtml view of the Home Controller. Once you add the Error view, copy
and paste the following code. The following view will be executed when your application has
an unhandled exception.
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Error</title>

</head>

<body>

<hgroup>

<h1 style="color:red">Unknwo Error</h1>

<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>

</hgroup>

</body>

</html>
Now, set the environment to Production or Staging and run the application, and you should
see the following generic error message.
401

How Do We Create a Custom Exception Filter in ASP.NET Core MVC?


As we already discussed, we can create the Custom Exception Filter in two ways: First, we
can create a class implementing the IExceptionFilter interface and
provide implementations for the [OnException] method. Second, we can create a class
inherited from the ExceptionFilterAttribute class and override the [OnException] method.
Let us see an example of creating a custom exception filter that logs the exception details.
The following are the steps to create and use a Custom Exception filter in ASP.NET Core
MVC Application:
Create a Custom Exception Filter Class
So, create a class file named CustomExceptionFilter.cs and copy and paste the following
code. The class inherits from the ExceptionFilterAttribute and
overrides the OnException method, where we write our custom logic. In the code below,
we fetch the Controller Name, Action Name, and Exception message from the Context
object and then log the details into a text file.
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomExceptionFilter : ExceptionFilterAttribute

public override void OnException(ExceptionContext context)

var controllerName = context.RouteData.Values["controller"];

var actionName = context.RouteData.Values["action"];

string message = $"\nTime: {DateTime.Now}, Controller: {controllerName}, Action:


{actionName}, Exception: {context.Exception.Message}";

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt


402

File.AppendAllText(filePath, message);

}
Applying the Custom Exception Filter:
We can apply the Custom Exception Filter at three levels: Globally, At the Action Method
Level, and At the Controller Level. We have created the Custom Exception Filter inheriting
from the ExceptionFilterAttribute, which means our Custom Exception Filter is also
an Attribute. As it is an Attribute, we can directly apply it to the Controller or Action
Methods. Let us modify the Home Controller as follows to apply the Custom Exception Filter
on the Index Action method.
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[CustomExceptionFilter]

public ActionResult Index()

int x = 10;

int y = 0;

int z = x / y;

return View();

}
403

Note: If you create the Custom Exception Filter by implementing


the IExceptionFilter or IAsyncExceptionFilter interface, you cannot apply that filter
directly to the Controllers or Action Methods. In that case, you need to use the Built-
in TypeFilter vs ServiceFilter Attribute at the Controller and Action Methods to specify the
Custom Filters. These two Built-in Filter Attributes are specifically designed for this purpose.
In our upcoming articles, we will discuss the differences Between TypeFilter and
ServiceFilter in ASP.NET Core MVC.
Registering the Filter Globally
We can also register the filter globally to apply to all actions in the application. This can be
done in the Program.cs class file as follows:
builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomExceptionFilter());

});
Creating Custom Exception Filter by Implementing IExceptionFilter
Interface:
Let us see how we can implement the IExceptionFilter Interface and provide
implementations for the OnException method to create a Custom Exception Filter. Let us
modify the CustomExceptionFilter class as follows, which inherits from the IExceptionFilter
Interface and implements the OnException method. As part of the OnException method, we
have written our custom exception logic, which will log the unhandled exception details to a
text file:
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomExceptionFilter : IExceptionFilter

public void OnException(ExceptionContext context)

var controllerName = context.RouteData.Values["controller"];

var actionName = context.RouteData.Values["action"];

string message = $"\nTime: {DateTime.Now}, Controller: {controllerName}, Action:


{actionName}, Exception: {context.Exception.Message}";
404

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt

File.AppendAllText(filePath, message);

}
Note: You need to remember that the above CustomExceptionFilter class is no longer an
Attribute. Hence, we cannot apply this CustomExceptionFilter as an Attribute in our
Controller or Action Method.
Register the Custom Exception Filter Globally
You can register the custom exception filter globally in the Program.cs file as follows. This
is for Global registration:
// Global registration

builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomExceptionFilter());

});

OR

builder.Services.AddControllersWithViews(options =>

options.Filters.Add(typeof(CustomExceptionFilter));

});
You can apply the custom exception filter to specific controllers or actions
using ServiceFilter or TypeFilter attributes. So, modify the Home Controller as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

{
405

public class HomeController : Controller

[ServiceFilter(typeof(CustomExceptionFilter))]

public ActionResult Index()

int x = 10;

int y = 0;

int z = x / y;

return View();

}
So, by following these steps, we can create and apply a Custom Exception Filter in our
ASP.NET Core MVC Application.
Redirecting to an Error View
Let’s see an example of how the Exception Filter redirects the user to a Custom Error view
when an unhandled exception occurs while processing the request. This is similar to the
example we created with the UseExceptionHandler middleware component. Let’s create the
error view within the Views/Shared folder named Error.cshtml and then copy and paste
the following code.
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Error</title>
406

</head>

<body>

<hgroup>

<h1 style="color:red">Unknwon Error</h1>

<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>

</hgroup>

</body>

</html>
Creating the Custom Exception Filter Attribute:
using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class RedirectToErrorViewFilter : ExceptionFilterAttribute

public override void OnException(ExceptionContext context)

context.Result = new ViewResult

ViewName = "Error"

};

context.ExceptionHandled = true;

}
407

}
Next, modify the Home Controller as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[RedirectToErrorViewFilter]

public ActionResult Index()

// Simulate an authorization exception

throw new UnauthorizedAccessException("Access Denied.");

}
When Should We Use Exception Filter in ASP.NET Core MVC?
The Custom Exception filters are useful in the following scenarios:
 Centralized Exception Handling: If you want a single, centralized location to
handle all the unhandled exceptions that occur throughout your application,
exception filters are a good choice.
 Error Logging: Exception filters provide a convenient mechanism for logging
exceptions or collecting error information. You can log exceptions to various
targets (e.g., files, databases, or external services).
 Custom Error Responses: Exception filters are helpful when you need to
customize the HTTP response sent to the client in case of an exception. You
can set specific HTTP status codes, return custom error messages, and
format responses according to your application’s requirements.
Difference Between Exception Handler and Exception Filter in
ASP.NET Core
408

In ASP.NET Core, Exception Handlers (UseExceptionHandler) and Exception Filters


(Custom Exception Filters) handle unhandled exceptions that occur during application
execution. Let us proceed and understand the differences between them:
Exception Handler in ASP.NET Core
The Exception Handler in ASP.NET Core is used to catch unhandled exceptions that occur
during the processing of HTTP requests in the middleware pipeline. It catches exceptions
globally for the entire application. The following are the key points of the Exception Handler.
 Typically configured in the Program.cs file.
 Use app.UseExceptionHandler to configure a global exception handler.
 It is useful for catching exceptions that occur in the middleware before the
execution reaches the MVC action methods.
 A typical use case is configuring a centralized error handling page or error
logging middleware.
 It is often used to return generic error responses in production to hide
sensitive error information from end-users.
Exception Filter in ASP.NET Core
The Exception Filters in ASP.NET Core apply custom error-handling logic to handle
unhandled exceptions thrown by controller action methods. The following are the key points
of the Exception Filter.
 It is implemented as part of the MVC pipeline.
 It can be applied globally, per-controller or per-action, using attributes.
 It is useful for handling exceptions specific to controller actions, such as
customizing the response based on the type of exception thrown by an
action.
 A common use case is to catch domain-specific exceptions in a controller and
return a specific HTTP status code or view.
 It is often used to transform the exception into a custom error view or a JSON
response.
Key Differences Between Exception Handler and Exception Filter in ASP.NET Core
 Context of Operation: Exception handlers work at the middleware level and
handle global exceptions across the entire application. Exception filters are
part of the MVC filter pipeline and handle the exceptions thrown by controller
actions.
 Configuration: Exception handlers are configured in the middleware
pipeline, usually in the Program.cs file. Exception filters are configured in the
MVC pipeline and can be applied using attributes.

Exception Filter in ASP.NET Core MVC


In this article, I will discuss the Exception Filter in ASP.NET Core MVC Application with
Examples. Please read our previous article discussing the basic concepts of Filters in
ASP.NET Core MVC Applications. As part of this article, we will discuss the following
pointers in detail.
1. What is an Exception Filter in ASP.NET Core MVC?
2. Built-in Exception Filter in ASP.NET Core MVC
3. Example to understand UseExceptionHandler Middleware
409

4. How Do We Create a Custom Exception Filter in ASP.NET Core MVC?


5. Differences Between Exception Handler and Exception Filter in ASP.NET
Core.
What is an Exception Filter in ASP.NET Core MVC?
In ASP.NET Core MVC, Exception Filter allows us to handle unhandled exceptions that
occur while processing an HTTP request within our application. Exception Filters in
ASP.NET Core Applications are used for tasks such as Logging Exceptions, Returning
Custom Error Responses, and Performing any other necessary action when an exception
occurs.
Exception Filter also provides a way to centralize the exception-handling logic and keep it
separate from our controller or from the business and data access logic, making our code
more organized and maintainable.
Built-In Exception Filter in ASP.NET Core MVC
The ASP.NET Core Framework does not provide any specialized Built-in Exception Filter
for Global Error Handling. As developers, we generally use the UseExceptionHandler
middleware component to handle exceptions globally.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
However, we can also create a Custom Exception Filter to handle unhandled exceptions
globally. In ASP.NET Core, we can create the Custom Exception Filter in two ways: First,
we can create a class implementing the IExceptionFilter interface and
provide implementations for the [OnException] method. Second, by creating a class
inherited from the ExceptionFilterAttribute class and overriding
the [OnException] method.
Example to understand UseExceptionHandler Middleware in ASP.NET
Core
Let us first see an example of how the UseExceptionHandler middleware component
handles unhandled exceptions globally. Then, we will see how to create a Custom
Exception Filter.
The UseExceptionHandler Middleware allows us to specify an error-handling path that will
be executed when an unhandled exception is thrown within our application. We can
configure this using the Main method of our Program class.
To configure UseExceptionHandler middleware for global Exception Handling, please add
the following code to the program class. With the following code in place, if an exception
occurs and if the environment is not Development, then the user will be redirected to the
“/Home/Error” URL. Here, Home is the Controller name, and Error is the action method
within the Home Controller. If the environment is Development, it will display the complete
error details using the UseDeveloperExceptionPage middleware component.
if (app.Environment.IsDevelopment())

app.UseDeveloperExceptionPage();

}
410

else

// This will handle exceptions and redirect to the specified error page.

app.UseExceptionHandler("/Home/Error");

}
Modify the Home Controller:
Next, modify the Home Controller as follows. As you can see in the code below, within the
Index action method, we have written the logic in a way that it will throw runtime exceptions
while executing it. Further, if you notice, we have not handled that exception within the
Index action method. Again, we have added the Error action method, which should be
executed when there is an unhandled exception and if the environment is not set to be
Development.
using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

public ActionResult Index()

int x = 10;

int y = 0;

int z = x / y;

return View();

public ActionResult Error()

return View();
411

}
Next, add the Error.cshtml view of the Home Controller. Once you add the Error view, copy
and paste the following code. The following view will be executed when your application has
an unhandled exception.
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Error</title>

</head>

<body>

<hgroup>

<h1 style="color:red">Unknwo Error</h1>

<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>

</hgroup>

</body>

</html>
Now, set the environment to Production or Staging and run the application, and you should
see the following generic error message.
412

How Do We Create a Custom Exception Filter in ASP.NET Core MVC?


As we already discussed, we can create the Custom Exception Filter in two ways: First, we
can create a class implementing the IExceptionFilter interface and
provide implementations for the [OnException] method. Second, we can create a class
inherited from the ExceptionFilterAttribute class and override the [OnException] method.
Let us see an example of creating a custom exception filter that logs the exception details.
The following are the steps to create and use a Custom Exception filter in ASP.NET Core
MVC Application:
Create a Custom Exception Filter Class
So, create a class file named CustomExceptionFilter.cs and copy and paste the following
code. The class inherits from the ExceptionFilterAttribute and
overrides the OnException method, where we write our custom logic. In the code below,
we fetch the Controller Name, Action Name, and Exception message from the Context
object and then log the details into a text file.
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomExceptionFilter : ExceptionFilterAttribute

public override void OnException(ExceptionContext context)

var controllerName = context.RouteData.Values["controller"];

var actionName = context.RouteData.Values["action"];

string message = $"\nTime: {DateTime.Now}, Controller: {controllerName}, Action:


{actionName}, Exception: {context.Exception.Message}";

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt


413

File.AppendAllText(filePath, message);

}
Applying the Custom Exception Filter:
We can apply the Custom Exception Filter at three levels: Globally, At the Action Method
Level, and At the Controller Level. We have created the Custom Exception Filter inheriting
from the ExceptionFilterAttribute, which means our Custom Exception Filter is also
an Attribute. As it is an Attribute, we can directly apply it to the Controller or Action
Methods. Let us modify the Home Controller as follows to apply the Custom Exception Filter
on the Index Action method.
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[CustomExceptionFilter]

public ActionResult Index()

int x = 10;

int y = 0;

int z = x / y;

return View();

}
414

Note: If you create the Custom Exception Filter by implementing


the IExceptionFilter or IAsyncExceptionFilter interface, you cannot apply that filter
directly to the Controllers or Action Methods. In that case, you need to use the Built-
in TypeFilter vs ServiceFilter Attribute at the Controller and Action Methods to specify the
Custom Filters. These two Built-in Filter Attributes are specifically designed for this purpose.
In our upcoming articles, we will discuss the differences Between TypeFilter and
ServiceFilter in ASP.NET Core MVC.
Registering the Filter Globally
We can also register the filter globally to apply to all actions in the application. This can be
done in the Program.cs class file as follows:
builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomExceptionFilter());

});
Creating Custom Exception Filter by Implementing IExceptionFilter
Interface:
Let us see how we can implement the IExceptionFilter Interface and provide
implementations for the OnException method to create a Custom Exception Filter. Let us
modify the CustomExceptionFilter class as follows, which inherits from the IExceptionFilter
Interface and implements the OnException method. As part of the OnException method, we
have written our custom exception logic, which will log the unhandled exception details to a
text file:
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomExceptionFilter : IExceptionFilter

public void OnException(ExceptionContext context)

var controllerName = context.RouteData.Values["controller"];

var actionName = context.RouteData.Values["action"];

string message = $"\nTime: {DateTime.Now}, Controller: {controllerName}, Action:


{actionName}, Exception: {context.Exception.Message}";
415

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt

File.AppendAllText(filePath, message);

}
Note: You need to remember that the above CustomExceptionFilter class is no longer an
Attribute. Hence, we cannot apply this CustomExceptionFilter as an Attribute in our
Controller or Action Method.
Register the Custom Exception Filter Globally
You can register the custom exception filter globally in the Program.cs file as follows. This
is for Global registration:
// Global registration

builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomExceptionFilter());

});

OR

builder.Services.AddControllersWithViews(options =>

options.Filters.Add(typeof(CustomExceptionFilter));

});
You can apply the custom exception filter to specific controllers or actions
using ServiceFilter or TypeFilter attributes. So, modify the Home Controller as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

{
416

public class HomeController : Controller

[ServiceFilter(typeof(CustomExceptionFilter))]

public ActionResult Index()

int x = 10;

int y = 0;

int z = x / y;

return View();

}
So, by following these steps, we can create and apply a Custom Exception Filter in our
ASP.NET Core MVC Application.
Redirecting to an Error View
Let’s see an example of how the Exception Filter redirects the user to a Custom Error view
when an unhandled exception occurs while processing the request. This is similar to the
example we created with the UseExceptionHandler middleware component. Let’s create the
error view within the Views/Shared folder named Error.cshtml and then copy and paste
the following code.
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Error</title>
417

</head>

<body>

<hgroup>

<h1 style="color:red">Unknwon Error</h1>

<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>

</hgroup>

</body>

</html>
Creating the Custom Exception Filter Attribute:
using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class RedirectToErrorViewFilter : ExceptionFilterAttribute

public override void OnException(ExceptionContext context)

context.Result = new ViewResult

ViewName = "Error"

};

context.ExceptionHandled = true;

}
418

}
Next, modify the Home Controller as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[RedirectToErrorViewFilter]

public ActionResult Index()

// Simulate an authorization exception

throw new UnauthorizedAccessException("Access Denied.");

}
When Should We Use Exception Filter in ASP.NET Core MVC?
The Custom Exception filters are useful in the following scenarios:
 Centralized Exception Handling: If you want a single, centralized location to
handle all the unhandled exceptions that occur throughout your application,
exception filters are a good choice.
 Error Logging: Exception filters provide a convenient mechanism for logging
exceptions or collecting error information. You can log exceptions to various
targets (e.g., files, databases, or external services).
 Custom Error Responses: Exception filters are helpful when you need to
customize the HTTP response sent to the client in case of an exception. You
can set specific HTTP status codes, return custom error messages, and
format responses according to your application’s requirements.
Difference Between Exception Handler and Exception Filter in
ASP.NET Core
419

In ASP.NET Core, Exception Handlers (UseExceptionHandler) and Exception Filters


(Custom Exception Filters) handle unhandled exceptions that occur during application
execution. Let us proceed and understand the differences between them:
Exception Handler in ASP.NET Core
The Exception Handler in ASP.NET Core is used to catch unhandled exceptions that occur
during the processing of HTTP requests in the middleware pipeline. It catches exceptions
globally for the entire application. The following are the key points of the Exception Handler.
 Typically configured in the Program.cs file.
 Use app.UseExceptionHandler to configure a global exception handler.
 It is useful for catching exceptions that occur in the middleware before the
execution reaches the MVC action methods.
 A typical use case is configuring a centralized error handling page or error
logging middleware.
 It is often used to return generic error responses in production to hide
sensitive error information from end-users.
Exception Filter in ASP.NET Core
The Exception Filters in ASP.NET Core apply custom error-handling logic to handle
unhandled exceptions thrown by controller action methods. The following are the key points
of the Exception Filter.
 It is implemented as part of the MVC pipeline.
 It can be applied globally, per-controller or per-action, using attributes.
 It is useful for handling exceptions specific to controller actions, such as
customizing the response based on the type of exception thrown by an
action.
 A common use case is to catch domain-specific exceptions in a controller and
return a specific HTTP status code or view.
 It is often used to transform the exception into a custom error view or a JSON
response.
Key Differences Between Exception Handler and Exception Filter in ASP.NET Core
 Context of Operation: Exception handlers work at the middleware level and
handle global exceptions across the entire application. Exception filters are
part of the MVC filter pipeline and handle the exceptions thrown by controller
actions.
 Configuration: Exception handlers are configured in the middleware
pipeline, usually in the Program.cs file. Exception filters are configured in the
MVC pipeline and can be applied using attributes.

Error Pages Based on Status Code in ASP.NET Core MVC


In this article, I will discuss Error Pages Based on Status Codes in ASP.NET Core
MVC Applications with Examples. Please read our previous article discussing Handling
Non-Success HTTP Status Codes in ASP.NET Core MVC Application.
HTTP Status Codes:
HTTP Status Codes are used to indicate the outcome of an HTTP Request. Non-success
HTTP status codes indicate errors or specific conditions that have occurred during the
request-response cycle. In ASP.NET Core MVC, you can handle these non-success status
420

codes to provide meaningful responses to the client. The following are some of the standard
non-success HTTP status codes in ASP.NET Core MVC:
 400 Bad Request: This status code is used when the server cannot
understand the request due to invalid syntax or client-side errors.
 401 Unauthorized: This status code indicates the request lacks valid
authentication credentials.
 403 Forbidden: This status code is used when the server understands the
request but refuses to fulfill it, often due to insufficient permissions.
 404 Not Found: This status code indicates that the requested resource could
not be found on the server.
 500 Internal Server Error: This status code is a generic error message
indicating that something has gone wrong on the server.
 503 Service Unavailable: This status code is used when the server is
temporarily unavailable to handle the request due to being overloaded or
undergoing maintenance.
Error Pages Based on Status Code in ASP.NET Core MVC
In ASP.NET Core MVC, we can create custom error pages based on Non-Success HTTP
status codes to provide a more user-friendly message when errors occur. Let us proceed
and see how to create and return Custom Error Pages based on Non-Success HTTP Status
Codes like 401, 404, 500, etc., in an ASP.NET Core MVC application.
As we already discussed in our previous article, we can configure the Error Handling
Middleware
(UseStatusCodePagesWithReExecute or UseStatusCodePagesWithRedirects middlew
are in the Program.cs file) to handle the Non-Success HTTP status code and render the
appropriate custom error page.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:21 / 03:1710 Sec

Create Custom Error Views:


First, create specific error views for specific status codes you want to handle. These views
will be displayed to the user when the corresponding HTTP status code is returned.
For example, you can create views like PageNotFoundError.cshtml for handling “Not
Found” errors (HTTP status code 404) and InternalServerError.cshtml for handling server
errors (HTTP status code 500). You must place these views inside
the Views/Shared directory.
Views/Shared/PageNotFoundError.cshtml
Views/Shared/InternalServerError.cshtml
Views/Shared/UnauthorizedError.cshtml
Views/Shared/GenericError.cshtml
Configuring Error Handling Middleware in ASP.NET Core:
In the Program.cs file, we need to configure the Error Middleware to return Custom Rrror
Mages based on HTTP Status Codes. To use the UseStatusCodePagesWithReExecute
middleware, add the following code to the Configure method:
app.UseStatusCodePagesWithReExecute(“/Error/{0}”);
This configuration tells ASP.NET Core to re-execute the request with the “Error” Route and
pass the status code as a parameter to the action method. To use the
UseStatusCodePagesWithRedirects middleware, you can use the following code:
421

app.UseStatusCodePagesWithRedirects(“/Error/{0}”);
This configuration will redirect the user to the Error Route with the corresponding status
code in the URL. Let us proceed and implement this example step by step:
Creating Error Controller:
Create a controller with the name ErrorController within the Controllers folders. Once you
create the Error Controller, please copy and paste the following code. When an error status
code is encountered, the Index action in the ErrorController is executed with the status code
as a parameter, and based on the status code, a specific error view is returned.
using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class ErrorController : Controller

[Route("Error/{statusCode}")]

public IActionResult Index(int statusCode)

Response.Clear();

Response.StatusCode = statusCode;

switch (statusCode)

case 401:

return View("UnauthorizedError");

case 404:

return View("PageNotFoundError");

case 500:

return View("InternalServerError");

default:

return View("GenericError");
422

}
Creating Error Views:
Next, add the following views to the Views/Shared folder.
PageNotFoundError.cshtml
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>PageNotFound Error</title>

</head>

<body>

<hgroup>

<h1 style="color:red">Page Not Found Error</h1>

<h2 style ="color:red">The Page you are trying to access is no longer available. Kindly
check and submit the URL again</h2>

</hgroup>

</body>

</html>
UnauthorizedError.cshtml
@{
423

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Unauthorized Error</title>

</head>

<body>

<hgroup>

<h1 style="color:red">Unauthorized Error</h1>

<h2 style="color:red">You donot have the permission to access this page. Kindly contact
with your admin.</h2>

</hgroup>

</body>

</html>
InternalServerError.cshtml
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />


424

<title>Internal Server Error</title>

</head>

<body>

<hgroup>

<h1 style="color:red">Internal Server Error</h1>

<h2 style="color:red">Some Internal Server error Occurred while processing your request.
Kindly try after some time.</h2>

</hgroup>

</body>

</html>
GenericError.cshtml
@{

Layout = null;

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Generic Error</title>

</head>

<body>

<hgroup>

<h1 style="color:red">Generic Error</h1>

<h2 style="color:red">An unknown error has occurred. We are working on it. Please try
after some time</h2>
425

</hgroup>

</body>

</html>
Configuring Middleware in Program.cs:
namespace FiltersDemo

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())

app.UseDeveloperExceptionPage();

else

app.UseStatusCodePagesWithReExecute("/Error/{0}");

app.UseHttpsRedirection();

app.UseStaticFiles();
426

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

}
The following line of code configures the Middleware:
app.UseStatusCodePagesWithReExecute(“/Error/{0}”);
Modify Home Controller:
Next, modify the Home Controller as follows. Here, you can see that we have created
different action methods to demonstrate the different types of Non-Success HTTP Status
Codes. SomeAction1 will throw a 400 Bad Request HTTP Status Code. SomeAction2 will
throw a 401 Unauthorized HTTP Status Code. SomeAction3 will throw a 403 Forbidden
HTTP Status Code. SomeAction4 will throw a 404 Not Found HTTP Status Code.
SomeAction5 will throw a 500 Internal Server Error HTTP Status Code. SomeAction6 will
throw a 503 Service Unavailable Error HTTP Status Code.
using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

public ActionResult Index()

return View();

//400 Bad Request


427

public IActionResult SomeAction1()

var someConditionIsNotMet = true;

if (someConditionIsNotMet)

return new StatusCodeResult(400);

// Other logic

return View();

//401 Unauthorized

public IActionResult SomeAction2()

var IsAuthenticated = false;

if (!IsAuthenticated)

return Unauthorized(); // This will return a 401 status code

// Other logic

return View();

//403 Forbidden

public IActionResult SomeAction3()

{
428

var UserHasPermissionToAccessResource = false;

if (!UserHasPermissionToAccessResource)

return new StatusCodeResult(403);

// Other logic

return View();

//404 Not Found

public IActionResult SomeAction4()

var requestedResourceNotFound = true;

if (requestedResourceNotFound)

return NotFound();

// Other logic

return View();

//500 Internal Server Error

public IActionResult SomeAction5()

try

{
429

// Some code that might throw an exception

// ...

throw new Exception("Some Exception Occurred");

return Ok(); // If successful

catch (Exception ex)

// Log the exception

return new StatusCodeResult(500);

//503 Service Unavailable

public IActionResult SomeAction6()

var isServiceUnavailable = true;

if (isServiceUnavailable)

return new StatusCodeResult(503);

// Other logic

return View();

}
430

Now, run the application and access the Home Controller Action Methods. You should see
the respective error pages based on the Status Code.

Result Filters in ASP.NET Core MVC


In this article, I will discuss Result Filters in ASP.NET Core MVC Applications with
Examples. Please read our previous article discussing Error Pages Based on Status
Code in ASP.NET Core MVC Application.
What are Result Filters in ASP.NET Core MVC?
In ASP.NET Core MVC, Result Filters are a specific type of filter that runs after the action
method has been executed but before the result is processed and sent to the client. This
means that with Result Filters, we can modify the view or the result data before it is returned
to the client.
How Do We Create a Result Filter in ASP.NET Core MVC?
In ASP.NET Core MVC, we can create a Custom Result Filter in two ways: First, by creating
a class implementing the IResultFilter interface and providing implementations
for [OnResultExecuting] and [OnResultExecuted] methods. Second, a class can be
created by inheriting the ResultFilterAttribute class and overriding
the [OnResultExecuting] and [OnResultExecuted] methods.
 Before Result Execution (OnResultExecuting): This method is executed
just before the action result is executed. You can use this method to modify
the action result or insert additional processing before the result is handled.
 After Result Execution (OnResultExecuted): This method is called after
the action result execution is completed. It allows us to modify the response,
log information, handle exceptions, or perform other operations after the
result has been processed.
Note: If you need to perform asynchronous operations within the Custom Result Filter, you
should implement the IAsyncResultFilter interface or AsyncResultFilterAttribute and
need to implement or override the OnResultExecutionAsync method. First, I will show you
the example using ResultFilterAttribute, and then I will show you another example using
the IResultFilter interface.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec

When Should We Use Result Filters in ASP.NET Core MVC?


 Modifying Results: Result filters can modify or replace the result being
executed. For example, you could change the view or data returned by an
action based on certain conditions.
 Logging: They provide a convenient place to log the use of particular actions
or results, such as response size or execution time.
 Custom Headers: Custom headers are added to the HTTP response based
on certain conditions evaluated before or after the action result.
Example to Understand Result Filter in ASP.NET Core MVC:
Let us look at one example of understanding result filters in ASP.NET Core MVC. Imagine
you are developing a web application that includes a feature to log the execution time of
431

certain pages and modify the HTTP response based on the user role. For example, we are
going to dynamically modify the view based on a query string (admin=true changes the
result to “AdminView”).
Define Result Filter:
First, create a custom result filter that measures execution time and appends a custom
header if the execution time exceeds a predefined threshold. So, create a class file
named CustomResultFilterAttribute.cs within the Models folder and copy and paste the
following code.
using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.AspNetCore.Mvc;

using System.Diagnostics;

namespace FiltersDemo.Models

public class CustomResultFilterAttribute : ResultFilterAttribute

private Stopwatch _timer;

public override void OnResultExecuting(ResultExecutingContext context)

// Initialize and start the timer

_timer = Stopwatch.StartNew();

// Add a custom header before executing the result

context.HttpContext.Response.Headers.Append("X-Pre-Execute", "Header set before


execution");

// Example: Modify the result based on authorization (dummy condition here)

if (context.HttpContext.Request.Query.ContainsKey("admin") && context.Result is


ViewResult viewResult)

context.Result = new ViewResult


432

ViewName = "AdminView",

ViewData = viewResult.ViewData,

TempData = viewResult.TempData

};

base.OnResultExecuting(context);

public override void OnResultExecuted(ResultExecutedContext context)

_timer.Stop();

var actionName = context.ActionDescriptor.DisplayName;

var executionTime = _timer.ElapsedMilliseconds;

var resultType = context.Result.GetType().Name;

// Log details about the action execution

Debug.WriteLine($"Action '{actionName}' executed in {executionTime} ms, resulting in


{resultType}");

base.OnResultExecuted(context);

}
OnResultExecuting Method
The OnResultExecuting method is called just before the action result is executed, i.e.,
before the framework writes the response. Here’s what happens in this method:
 Timer Initialization: A Stopwatch instance is created and started to measure
the duration of the result’s execution. This is useful for performance
monitoring.
433

 Setting a Custom Header: The HTTP response includes a custom header X-


Pre-Execute. This header is set before the result is actually executed,
allowing you to send additional information in the HTTP headers.
 Modifying the Result Conditionally: The method checks if the incoming
request contains a specific query parameter (admin). If this condition is met,
the method changes the view being returned. It sets the view to AdminView
instead of the original view, effectively altering the output based on the
request parameters.
 Calling Base Method: Finally, it calls base.OnResultExecuting(context) to
ensure that any logic in the base class is also executed.
OnResultExecuted Method
The OnResultExecuted method is called right after the action result is executed, i.e., after
the result has been processed and the response has likely been sent to the client. Here’s
what happens in this method:
 Stop Timer: It stops the Stopwatch instance that was started in
OnResultExecuting to get the total execution time.
 Logging: It logs details about the action, such as the action name, execution
time, and the type of result. This is crucial for debugging and monitoring the
behavior of your web application.
 Calling Base Method: It calls base.OnResultExecuted(context) to ensure
that any logic in the base class is also executed.
Modifying Home Controller:
You can apply this filter to a specific action, a controller, or globally across all controllers.
Here, we apply it to the Home Controller only. Next, modify the Home Controller as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

[CustomResultFilter]

public class HomeController : Controller

public IActionResult Index()

// The view name can be dynamically changed based on the filter

return View();

}
434

}
Index.cshtml
Next, modify the Index.cshtml view as follows:
@{

ViewData["Title"] = "Home Page";

<h1>Normal User View</h1>


AdminView.cshtml
Next, add the AdminView.cshtml view within the Views/Home folder and then copy and
paste the following code:
@{

ViewData["Title"] = "AdminView";

<h1>Admin Panel</h1>
Running and Testing the Application
Access /Home/Index normally to see the “Index View”. I

Access /Home/Index?admin=true to trigger the admin view condition.


435

In both requests, if you check the response headers, then you will see the custom header,
which we set using the Result Filter as shown in the below image:

Custom Result Filter using IAsyncResultFilter Interface:


Now, let us rewrite the previous example using IAsyncResultFilter. We need to implement
the IAsyncResultFilter interface and needs to provide the necessary asynchronous handling
for the result execution and post-execution events. Create a class file named
CustomResultFilter.cs and then copy and paste the following code:
using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.AspNetCore.Mvc;

using System.Diagnostics;

namespace FiltersDemo.Models

public class CustomResultFilter : IAsyncResultFilter

private Stopwatch _timer;

public async Task OnResultExecutionAsync(ResultExecutingContext context,


ResultExecutionDelegate next)

// Initialize and start the timer


436

_timer = Stopwatch.StartNew();

// Add a custom header before executing the result

context.HttpContext.Response.Headers.Append("X-Pre-Execute", "Header set before


execution");

// Example: Modify the result based on authorization (dummy condition here)

if (context.HttpContext.Request.Query.ContainsKey("admin") && context.Result is


ViewResult viewResult)

context.Result = new ViewResult

ViewName = "AdminView",

ViewData = viewResult.ViewData,

TempData = viewResult.TempData

};

// Execute the result as planned

var executedContext = await next();

// Stop the timer after the result is executed

_timer.Stop();

var actionName = context.ActionDescriptor.DisplayName;

var executionTime = _timer.ElapsedMilliseconds;

var resultType = executedContext.Result.GetType().Name;

// Log details about the action execution

Debug.WriteLine($"Action '{actionName}' executed in {executionTime} ms, resulting in


{resultType}");
437

}
Code Explanation:
 Initialization of Stopwatch: A Stopwatch is instantiated and started to
measure the time taken to execute the action result. This is useful for
performance monitoring.
 Setting Custom Header: Before the action result is executed, a custom
header (X-Pre-Execute) is added to the HTTP response. This could be used
for various purposes, such as providing metadata about the response or for
debugging purposes.
 Conditional Result Modification: The method checks if the HTTP request
contains a specific query parameter (admin). If this condition is met, and the
current result is a ViewResult (which typically renders a view), it modifies the
result to change the view that will be rendered.
 Execution of Result: The await next() call is very important. This line hands
over control to the next filter in the pipeline, or if there are no further filters, it
executes the action result. This is an asynchronous operation and the method
awaits its completion before proceeding. After await next() completes, it
returns a ResultExecutedContext, which contains details about the executed
action result.
 Stopwatch Stopping and Logging: Once the action result has been
executed and the control returns to the filter, the stopwatch is stopped. The
elapsed time (how long the result took to execute), the name of the executed
action, and the type of result that was executed are logged using
Debug.WriteLine().
Modifying the Home Controller:
Next, modify the Home Controller as follows. As the CustomResultFilter is not an attribute,
we cannot directly apply it to the Controller or Action method level. Here, we are using
TypeFilter to apply the Custom Result filter at the controller level.
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

[TypeFilter(typeof(CustomResultFilter))]

public class HomeController : Controller

{
438

public IActionResult Index()

// The view name can be dynamically changed based on the filter

return View();

}
With these changes, run the application, and it should work as expected.

Custom Result Filter in ASP.NET Core MVC


In this article, I will discuss How to Create a Custom Result Filter in an ASP.NET Core
MVC Application with Real-Time Examples. Please read our previous article
discussing Result Filter in ASP.NET Core MVC Application.
Custom Result Filter in ASP.NET Core MVC
In ASP.NET Core MVC, Result Filters are a specific type of filter that runs after the action
method has been executed but before the result is processed and sent to the client, i.e.,
before or after executing action results. Action results are what an action method returns to
generate a response. This could be a view, a file, a redirect, or a JSON result, among other
things. Custom Result Filters in ASP.NET Core MVC can be useful for:
 Modifying or Replacing the Result.
 Adding HTTP Headers to the Response.
 Logging or Auditing Response Data.
 Handling Errors.
 Caching Responses.
How Do We Create a Custom Result Filter in ASP.NET Core MVC?
In ASP.NET Core, we can create a Custom Result Filter in two ways: First, we can create a
class implementing the IResultFilter interface and providing implementations for
the OnResultExecuting and OnResultExecuted methods. Second, we can create a
class that inherits from the ResultFilterAttribute class and
overrides the OnResultExecuting and OnResultExecuted methods. So, creating and
using a Custom Result Filter in ASP.NET Core MVC involves the following steps:
 Define the Filter: Create a class that implements
the IResultFilter/IAsyncResultFilter interface or inherits from
the ResultFilterAttribute class. For synchronous operations, use
IResultFilter, and for asynchronous, use IAsyncResultFilter.
 Implement Required Methods: For IResultFilter, you need to
implement OnResultExecuting and OnResultExecuted.
For IAsyncResultFilter, you need to implement OnResultExecutionAsync.
439

If you are inheriting the ResultFilterAttribute class, you must override


the OnResultExecuting and OnResultExecuted methods.
 Apply the Filter: Once the Custom Result Filter is created, apply it to actions
or controllers using attributes or add it globally in the Program class.
Approach 1: Inheriting from ResultFilterAttribute Class
Create a Custom Result Filter by inheriting from the ResultFilterAttribute class and
overriding its methods: OnResultExecuting and OnResultExecuted. So, create a class
file named CustomResultFilterAttribute.cs and copy and paste the following code. In the
example below, we log when the OnResultExecuting and OnResultExecuted methods are
executed.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec

using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomResultFilterAttribute : ResultFilterAttribute

public override void OnResultExecuting(ResultExecutingContext context)

// This method is called before the result is executed.

LogMessage("Executing custom result filter attribute before result execution.\n");

public override void OnResultExecuted(ResultExecutedContext context)

// This method is called after the result has been executed.

LogMessage("Executing custom result filter attribute after result execution.\n");

private void LogMessage(string message)

{
440

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt

File.AppendAllText(filePath, message);

}
Applying the Custom Result Filter to an Action Method:
Let us modify the Home Controller as follows to apply the Custom Result Filter on the Index
Action method:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[CustomResultFilterAttribute]

public IActionResult Index()

return View();

}
With the above code in place, run the application, navigate to the Index action method, and
verify the Log.txt file, which should be generated within the Log folder.
Registering the Filter Globally
We can also register the filter globally to apply to all actions in the application. This can be
done in the Program.cs class file as follows:
builder.Services.AddControllersWithViews(options =>
441

options.Filters.Add(new CustomResultFilterAttribute());

});
Approach 2: Implementing IResultFilter (Synchronous Result Filter)
Let’s say we want to create a result filter that logs the type of ActionResult returned by our
action methods. So, create a Custom Result Filter by implementing
the IResultFilter interface. You need to implement two
methods: OnResultExecuting and OnResultExecuted. These methods are called before
and after the result is executed. So, modify the CustomResultFilter.cs as follows.
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomResultFilter : IResultFilter

public void OnResultExecuting(ResultExecutingContext context)

// This method is called before the result is executed.

// Log information before the action result starts executing

LogMessage($"Result type is about to be executed: {context.Result.GetType().Name}\n");

LogMessage("Executing custom result filter attribute before result execution.\n");

public void OnResultExecuted(ResultExecutedContext context)

// This method is called after the result has been executed.

// Log information after the action result has finished executing

LogMessage($"Result type has been executed: {context.Result.GetType().Name}\n");

LogMessage("Executing custom result filter attribute after result execution.\n");


442

private void LogMessage(string message)

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt

File.AppendAllText(filePath, message);

}
Register the CustomResultFilter as a service in your application’s Program.cs file. You can
specify the filter’s scope (e.g., Transient, Scoped, Singleton) based on your requirements.
builder.Services.AddScoped<CustomResultFilter>();
Apply the CustomResultFilter to your controller action methods using the [ServiceFilter]
attribute or add it globally in the Program.cs file. For example, apply on to the Action
method as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[ServiceFilter(typeof(CustomResultFilter))]

public IActionResult Index()

return View();

}
443

}
Adding Globally:
Adding the following code in the Program.cs class file:
//Adding Custom Result Filter Globally

builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomResultFilterAttribute());

});

builder.Services.AddScoped<CustomResultFilter>();
Approach 3: Implementing IAsyncResultFilter (Asynchronous Result
Filter)
Assume you want to compress the response’s content asynchronously in certain conditions.
You would implement an asynchronous result filter to compress the response stream.
using Microsoft.AspNetCore.Mvc.Filters;

using System.IO.Compression;

namespace FiltersDemo.Models

public class CompressResultFilter : Attribute, IAsyncResultFilter

public async Task OnResultExecutionAsync(ResultExecutingContext context,


ResultExecutionDelegate next)

// Check if the response should be compressed based on some logic

var shouldCompress = ShouldCompressResponse(context);

if (shouldCompress)

var originalBodyStream = context.HttpContext.Response.Body;


444

using (var compressedStream = new MemoryStream())

using (var compressionStream = new GZipStream(compressedStream,


CompressionMode.Compress, leaveOpen: true))

context.HttpContext.Response.Body = compressionStream;

// Execute the result (action filters, action, result filters, result)

await next();

// Flush the remaining data

await compressionStream.FlushAsync();

// Copy the compressed data to the original stream

compressedStream.Seek(0, SeekOrigin.Begin);

await compressedStream.CopyToAsync(originalBodyStream);

context.HttpContext.Response.Body = originalBodyStream;

context.HttpContext.Response.Headers.Add("Content-Encoding", "gzip");

else

// If we don't want to compress, just call the next delegate/middleware in the pipeline

await next();

}
445

private bool ShouldCompressResponse(ResultExecutingContext context)

// Your logic to determine if the response should be compressed

return true; // For this example, we're just compressing every response

}
In this asynchronous result filter example, the filter checks if the response should be
compressed. If so, it sets up a new response body stream that compresses the content.
After executing the action result (with await next();), it copies the compressed content to the
original response stream and sets the appropriate HTTP header.
Then, apply this filter:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[CompressResultFilter]

public IActionResult Index()

return View();

}
446

Response Caching in ASP.NET Core


In this article, I will discuss Response Caching (ResponseCacheAttribute) in ASP.NET
Core Application with Examples. Please read our previous article discussing Custom
Result Filter in ASP.NET Core MVC Application. At the end of this article, you will
understand the following pointers:
 What is Caching?
 Types of Caching in ASP.NET Core
 What is Response Caching in ASP.NET Core?
 How Response Caching Works in ASP.NET Core?
 Response Caching Examples: Basic, No Caching, Caching with
VarByHeader, Location, and VarByQueryKeys.
 Custom Cache Profiles in ASP.NET Core Response Caching.
 Benefits of Response Caching in ASP.NET Core?
What is Caching?
Caching is a technique for storing frequently accessed data in a temporary storage area
that can be quickly retrieved. This will improve the overall performance of the application by
reducing the need to fetch the same data repeatedly from the database or other storage
mediums.
Types of Caching in ASP.NET Core:
ASP.NET Core Supports Several Types of Caching Mechanisms. They are as follows:
 In-Memory Caching: This is the simplest form of caching, suitable for a
single server. It stores data in the Web Server’s Main Memory. It’s fast and
suitable for data that doesn’t consume too much memory and doesn’t need to
persist beyond the lifetime of the web server process. It is suitable for storing
small amounts of data.
 Distributed Caching: This is ideal for applications running in multi-server or
load-balancing environments where data needs to be shared across multiple
servers. It involves storing data in an external system such as Redis, SQL
Server, NCache, etc. It is more complex than in-memory caching but
necessary for large-scale applications to ensure consistency across sessions
and requests.
 Response Caching: Response Caching refers to the process of storing the
output of a request-response cycle in the cache so that future requests for the
same resource can be served from the cache instead of regenerating the
response from scratch. This technique can significantly improve a web
application’s performance, especially for resources that are expensive to
generate and don’t change often.
Note: In this session, we will discuss Response Caching. The rest will be discussed in our
upcoming sessions.

Enchanted by the Beautiful City near Cambodia Border - Nếm TV00:10 / 02:4810 Sec

What is Response Caching?


When a web application receives a request, it often performs several operations, such as
database queries, complex calculations, and template rendering, to generate the response.
These operations can be time-consuming. If the generated response is the same for each
request, it makes sense to store that response once it is generated and then serve the
447

stored response for future requests without regenerating the response again by doing all the
processing. This is called Response Caching in ASP.NET Core.
How Response Caching Works in ASP.NET Core?
Please look at the following diagram to understand how response caching works in the
ASP.NET Core Application.

Here’s how response caching works in a web context, particularly in ASP.NET Core Web
Application:
Initial Request:
 A client makes a request to the server for a specific resource.
 The server processes the request, generates the response, and returns the
response to the client who initially made the request.
 Along with the response, the server includes a Cache-Control HTTP
header to indicate that the response can be cached and to specify the
caching behavior.
Subsequent Requests:
 When the client (i.e., browser) needs the same resource again, it first checks
the cache.
 Suppose the cached response is available and fresh (i.e., within the period
specified by the Cache-Control header). In that case, the client serves it
directly from the cache instead of requesting it from the server again.
What is Proxy Cache in ASP.NET Core?
In the context of ASP.NET Core, Proxy Caches refer to a caching mechanism where a
Caching Server (the Proxy Server) is placed between the Client (usually a Web Browser)
and the Web Server hosting the ASP.NET Core application. This Proxy Server caches
responses from the Web Server, which can then be served quickly to subsequent requests
for the same resource. This can significantly improve the performance of web applications
by reducing the load on the web server and decreasing response times for end-users.
Server-Side:
 In ASP.NET Core, you can use the [ResponseCache] attribute to specify
caching behavior for controller actions. This attribute allows you to set various
448

parameters, such as duration, location (client, server, or both), and whether


the cache should consider query string values.
 ASP.NET Core includes UseResponseCaching middleware for caching
responses on the server. This middleware needs to be configured in the
Program.cs file.
Client-Side:
 Browsers respect the Cache Headers and store the responses in their cache
based on their directives, i.e., no-cache, no-store, public, private, etc.
 Proxy Caches can do the same, helping reduce the load on the server and
speeding up response times for users behind the proxy.
How Do We Implement Response Cacheing in ASP.NET Core?
To implement Response Caching, we need to use the ResponseCacheAttribute in
ASP.NET Core. This attribute allows developers to control how responses from Web
Applications are cached. It can be applied to controller actions or entire controllers and
specifies the parameters necessary for setting appropriate HTTP headers for caching
responses.
The ResponseCache Attribute itself does not cache content. Instead, it sets the appropriate
HTTP headers that control the caching behavior. The ResponseCache Attribute has the
following properties:
 Duration(int): Specifies the time, in seconds, for which the response should
be cached. This sets the max-age directive of the Cache-Control header.
 Location (ResponseCacheLocation): This setting modifies the Cache-
Control header and determines the location where the response can be
cached. Options include Any, Client, and None.
 NoStore (bool): When set to true, this property indicates that the response
should not be cached (Cache-Control: no-store). It takes precedence over
other properties.
 VaryByHeader (string): Specifies a header name to vary the cached
response by (e.g., User-Agent).
 VaryByQueryKeys(string): This option allows you to vary the response
cache based on the query string parameters. It’s useful for caching different
responses for different query string values.
 CacheProfileName(string): This setting refers to a cache profile defined in
the application’s configuration, which allows for centralized cache policy
settings.
Examples to Understand Response Caching in ASP.NET Core:
Let’s look at examples to understand how to use the ResponseCacheAttribute in an
ASP.NET Core Web API application to implement Response Caching. We will test the
functionality using Postman and ASP.NET Core Web API. It will not work as expected if
you test the same using Browser. This is because each browser manages the Caching in a
different way.
Configure the Response Caching Middleware and Services
Before using the ResponseCacheAttribute, ensure that the response caching middleware is
added to the request pipeline in the Program.cs class file. We need to add
the AddResponseCaching services and UseResponseCaching middleware components
to the request processing pipeline. So, modify the Program.cs class file as follows:
namespace ResponseCachingDemo
449

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

//Adding Response Caching Service

builder.Services.AddResponseCaching();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (app.Environment.IsDevelopment())

app.UseSwagger();

app.UseSwaggerUI();

app.UseHttpsRedirection();

//Adding Response Caching Middleware Components

app.UseResponseCaching();

app.UseAuthorization();
450

app.MapControllers();

app.Run();

}
Example to Understand Basic Response Caching
Next, modify the Home Controller as follows. Here, we have applied the Index action
method with the [ResponseCache(Duration = 60)] Attribute, which will cache the response
for 60 seconds. This means that once a response is generated, it will be reused for
subsequent requests for the next 60 seconds.
using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo.Controllers

[Route("api/[controller]/[action]")]

[ApiController]

public class HomeController : ControllerBase

[HttpGet]

[ResponseCache(Duration = 60)]

public string Index()

return $"Response Generated at: {DateTime.Now}";

}
451

Now, run the application and access the endpoint /api/home/index using Postman, and
you will see the following output. If you access the same endpoint within 60 seconds, you
will also see the same output.

Further, if you check the Response headers, you will see the Cache-Control header whose
value is set to public,max-age=60, as shown in the image below.

Cache-Control: public, max-age=60: These settings instruct all caches (like Browser,
Postman, Fiddler, Swagger, or Proxy Server) to store and reuse the response for 60
seconds without revalidating it. After 60 seconds, the cached response is considered
invalid, and a new copy needs to be fetched from the original server.
Example: No Caching
In the following example, we have set the NoStore property of
the ResponseCache Attribute to true, which tells the browser not to cache the response.
using Microsoft.AspNetCore.Mvc;
452

namespace ResponseCachingDemo.Controllers

[Route("api/[controller]/[action]")]

[ApiController]

public class HomeController : ControllerBase

[HttpGet]

[ResponseCache(Duration = 60, NoStore = true)]

public string Index()

return $"Response Generated at: {DateTime.Now}";

}
Now, run the application and access the endpoint /api/home/index using Postman, and you
will see the caching is not working. Every time you hit the endpoint, you will get a different
response even though we have set the cache duration to 60 seconds. This is because we
have set the NoStore Attribute value to true, which means caching is disabled. Now, if you
check the Response headers, you will see the Cache-Control header whose value is set
to no-store, as shown in the image below.
453

Cache-Control: no-store: This setting instructs all caches (like Browser, Postman, Fiddler,
Swagger, or Proxy Server) not to store any part of the response in the cache. The no-store
directive ensures that no copy of the response is saved in any cache. Each time the
resource is needed, it must be fetched directly from the server. This guarantees that the
information is retrieved in its current state without the risk of serving outdated or potentially
compromised data from a cache.
Example to Understand Response Caching with VaryByHeader
In ASP.NET Core, the VaryByHeader Parameter in Response Caching specifies a set of
Request Headers (User-Agent, Accept-Language, or any Custom Header) that should
trigger the cache to store multiple responses for the same URL based on their values. This
is useful when responses vary not only by URL but also by request headers.
When we configure the Response Cache with the VaryByHeader parameter, the caching
mechanism considers the URL and the specified header values to determine the cache key.
That means different responses are cached and retrieved based on variations in the
specified headers. For a better understanding, please modify the Home Controller as
follows. Here, we are using the User-Agent header.
using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo.Controllers

[Route("api/[controller]/[action]")]

[ApiController]

public class HomeController : ControllerBase

{
454

[HttpGet]

[ResponseCache(Duration = 60, VaryByHeader = "User-Agent")]

public string Index()

return $"Response Generated at: {DateTime.Now}";

}
Now, run the application and access the endpoint /api/home/index using Postman and
Swagger. You will get a different response for each client. This is because the User-Agenet
header for Swagger and Postman is going to be different. This is also true in the case of
Browsers. Each Web Browser has a different User Agent. When the User-Agent is
changed, it will fetch the data from the server. Now, if you check the Response header, then
you will see along with the Cache-Control header, the Server is also adding the Vary header
in the Response, as shown in the below image:

Example to Understand Response Caching with Location


By default, Caching is saved on both the client and server sides. Suppose we want to store
the cache response only on the client side; then, we need to use the Location property.
Using the Location property of the Response Cache Attribute, we can specify where we
want to store the response cache using the following ResponseCacheLocation enum.
455

To store the cache on the browse only, we need to use the Client Option.
The ResponseCacheLocation.Client specifies that the response should be cached only
on the client side, and in this case, it will set the Cache-Control header to Private. So,
modify the Home Controller as follows:
using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo.Controllers

[Route("api/[controller]/[action]")]

[ApiController]

public class HomeController : ControllerBase

[HttpGet]

[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client, VaryByHeader


= "User-Agent")]

public string Index()

return $"Response Generated at: {DateTime.Now}";

}
456

}
Now, you can test the functionality using Swagger, and you should see the following output.
It will not work on Postman.

Please check the following Key Points of the behavior of Swagger and Postman:
Swagger Behavior:
When testing APIs, Swagger UI generally respects the HTTP caching headers sent by the
server. This means that if your ASP.NET Core application sends a response with Cache-
Control set to private, max-age=300, Swagger UI will cache the response as instructed for
the duration specified. Subsequent requests to the same endpoint (within the cache
duration) might not hit the server if the cached response is deemed valid.
Postman Behavior:
Postman, on the other hand, does not automatically cache responses like a typical web
browser would, despite receiving the same caching headers. In Postman, every time you hit
the “Send” button, a new request is made to the server, ignoring any previous response
cache headers. This is intentional, as Postman is designed for testing and development,
where you often need to see live server responses without caching interference.
Cache-Control: Private: The Cache-Control header with the value private is used in
Response Caching to indicate that the response is specific to a single user and should not
457

be stored by shared caches, such as proxy servers. It can only be stored in the private
cache of the user’s browser.
Example to Understand Response Caching with VaryByQueryKeys
In ASP.NET Core, the VaryByQueryKeys Property in Response Caching is used to specify
that the response cache should vary based on the values of specified query string
parameters. This is useful when a URL serves different data based on query string
parameters. For example, if you have a URL that can return different results based on a
query string like ?page=1 or ?page=2, you can use VaryByQueryKeys to cache these
responses separately. For a better understanding, please modify the Home Controller as
follows:
using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo.Controllers

[Route("api/[controller]/[action]")]

[ApiController]

public class HomeController : ControllerBase

[HttpGet]

[ResponseCache(Duration = 60, VaryByQueryKeys = new[] { "page" })]

public string Index(int page)

return $"Response Generated at: {DateTime.Now}, for Page Number: {page}";

}
In this case, the cached output of the Index action method would differ depending on the
value of the page query string parameter. This means if a user requests Index?page=1 and
another user requests Index?page=2, they will each get a different cached response
specific to the page they requested.
Example to Understand Custom Cache Profile with Response Caching
In ASP.NET Core, Custom Cache Profiles allows us to define Reusable Caching Settings
that can be applied across multiple actions or controllers. These profiles are defined in the
458

application’s configuration, typically in the Program.cs class file, and can then be
referenced by its name in controllers. Let us modify the Program class as follows.
using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo

public class Program

public static void Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers(options =>

//Creating Custom Cache Profiles

options.CacheProfiles.Add("Default60", new CacheProfile()

Duration = 60,

Location = ResponseCacheLocation.Any

});

options.CacheProfiles.Add("NoCache", new CacheProfile()

Location = ResponseCacheLocation.None,

NoStore = true

});

});
459

//Adding Response Caching Service

builder.Services.AddResponseCaching();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (app.Environment.IsDevelopment())

app.UseSwagger();

app.UseSwaggerUI();

app.UseHttpsRedirection();

//Adding Response Caching Middleware Components

app.UseResponseCaching();

app.UseAuthorization();

app.MapControllers();

app.Run();

}
Here, we have configured two cache profiles (Default60 and NoCache), as shown in
the below image:
460

Using Cache Profile in Response Cache Attribute:


Once you define the Cache Profiles, they can be used within controller actions using
the CacheProfileName Property of the ResponseCache attribute by referring to the profile
name. So, modify the Home Controller as follows. Here, we have specified the Default60
Cache profile within the Index Action method and NoCache with the Privacy action method.
using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo.Controllers

[Route("api/[controller]/[action]")]

[ApiController]

public class HomeController : ControllerBase

[HttpGet]

[ResponseCache(CacheProfileName = "Default60")]

public string Index()

{
461

return $"Index Response Generated at: {DateTime.Now}";

[HttpGet]

[ResponseCache(CacheProfileName = "NoCache")]

public string Privacy()

return $"Privacy Response Generated at: {DateTime.Now}";

}
Now, run the application and access the above endpoints using either Swagger or Postman.
You will see that Response Caching works with the Index action method while it is disabled
with the Privacy Action method.
Benefits of Response Caching in ASP.NET Core MVC
The following are some of the key benefits of using Response Caching in ASP.NET Core
MVC:
 Improved Performance: By storing the output of actions, Response Caching
reduces the time and resources needed to generate responses on
subsequent requests. This can lead to faster page load times for users, as the
server can serve cached content instead of regenerating it each time.
 Reduced Server Load: Caching can dramatically reduce the workload on the
server. When content is served from the cache, fewer resources are
consumed, which means your server can handle more users and requests
with the same hardware.
 Enhanced User Experience: Faster response times generally lead to a
better user experience.
 Customizable Caching Strategies: ASP.NET Core offers a variety of
caching strategies, such as caching by duration, parameters, headers, etc.
This flexibility allows developers to optimize caching based on the
application’s needs and behaviors.

Authorization Filter in ASP.NET Core MVC


In this article, I will discuss the Authorization Filter in ASP.NET Core MVC Application
with Examples. Please read our previous article discussing Response Caching in
462

ASP.NET Core Applications. As part of this article, we will discuss the following pointers in
detail.
1. What is the Authorization Filter in ASP.NET Core MVC?
2. What is Authentication?
3. What is Authorization?
4. Examples to Understand Authorization Filter in ASP.NET Core MVC
5. Custom Authorization Filter in ASP.NET Core MVC
6. When Should We Use Authorization Filter in ASP.NET Core MVC?
What is the Authorization Filter in ASP.NET Core MVC?
In ASP.NET Core MVC, the Authorization Filter allows us to apply authorization rules to
controllers and actions within our application. Authorization Filters in ASP.NET Core are
responsible for checking whether a user is allowed to perform an action or access a
resource. These filters run before the action method is executed, ensuring the user has
permission to access the method.
Authorization filters are executed after the routing but before model binding and other action
filters. If the authorization fails (e.g., the user does not have the required permissions), the
filter short-circuits the request, and the action method does not execute.
What is Authentication?
Authentication is a process that ensures and confirms a user’s identity. In other words, we
can say that it is a process to validate someone against some data source. Let us
understand Authentication from a layman’s point of view. For this, please have a look at the
following diagram.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:23 / 03:1710 Sec

The above image shows the different sections of an IT Company, such as Reception, HR
Section, Accounts Section, Server Room, etc. At the gate, we have biometrics to verify the
employee. Suppose one employee comes. This biometrics checks the employee credentials
against some data source, and if it is found that the employee is valid, it only allows entering
into the campus. This is nothing but Authentication.
What is Authorization?
Authorization is a mechanism that determines whether users can access a particular
resource. The most important point that you need to remember is that authentication
happens first and then only authorization. For a better understanding, please have a look at
the following image.
463

As shown in the above image, once the employee is authenticated, he enters the Campus.
Then, Authorization comes into the picture. Within the campus, which section he is allowed
to enter is determined by the Authorization process. The Role of the Employee does this. If
the Employee has list privileges, he may not allow each section. On the other hand, if the
Employee has the highest privileges, he may be allowed to enter each section.
Types of Authorization Filters in ASP.NET Core MVC:
By default, in the ASP.NET Core MVC applications, all the action methods of all controllers
can be accessed by both authenticated and anonymous users. However, if you want the
action methods to be available only for authenticated and authorized users, you need to use
the Authorization Filter in ASP.NET Core MVC. ASP.NET Core provides two built-in
attributes, [Authorize] and [AllowAnonymous], that can be used as filters.
 Authorize Attribute: The [Authorize] Attribute specifies that only
authenticated users or users with certain roles or policies can access a
particular action method or controller. The [Authorize] attribute specifies that
the associated action method or controller requires the user to be
authenticated.
 AllowAnonymous Attribute: The [AllowAnonymous] attribute specifies that
an action method or controller should allow anonymous access, even when
an [Authorize] attribute is applied at the controller or action level. In other
words, it permits unauthenticated users to access the decorated resource,
bypassing any authentication and authorization checks that might be in place
at higher levels.
Examples to Understand Authorization Filter in ASP.NET Core MVC:
Let us understand the Authorize and AllowAnonymous Filters with an example. First, create
a new ASP.NET Core Application using the Model-View-Controller Project Template. Once
you create the Project, then modify the Home Controller as follows. As you can see, we
created the HomeController with three action methods,
i.e., NonSecureMethod, SecureMethod, and Login. We want the SecureMethod to be
accessed by authenticated users while the NonSecureMethod and Login methods to be
accessed by anyone. Whenever an unauthenticated user wants to access the
SecureMethod, we must redirect that user to the Login action method.
using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

{
464

public class HomeController : Controller

public string NonSecureMethod()

return "This method can be accessed by everyone as it is non-secure method";

public string SecureMethod()

return "This method needs to be access by authorized users as it SecureMethod";

public string Login()

return "This is the Login Page";

}
At this point, authenticated and anonymous users can access the SecureMethod and the
NonSecureMethod methods using the following two URLs.
/Home/SecureMethod
/Home/NonSecureMethod
If you want the Secure Method to be accessed only by authenticated and authorized users,
then you need to decorate this method with the Authorize attribute, as shown below.
using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller


465

public string NonSecureMethod()

return "This method can be accessed by everyone as it is non-secure method";

[Authorize] //Requires authentication for the SecureMethod

public string SecureMethod()

return "This method needs to be access by authorized users as it SecureMethod";

public string Login()

return "This is the Login Page";

}
Now run the application and navigate to /Home/SecureMethod. You will see the following
Internal Server Error.
466

Instead of displaying the above error page, we need to redirect the user to the Login Page.
In the later part of this article, I will show you how to create Custom Authentication Filters to
achieve the same in ASP.NET Core MVC Applications.
Can we apply the Authorize Attribute at the controller level?
Applying the Authorize attribute at the controller level applies to all the action methods
present within that controller. For a better understanding, please modify the Home
Controller as follows. The Home Controller Action methods are now protected with the
Authorize Attribute. So, only the authenticated users can now access the SecureMethod(),
NonSecureMethod(), and Login action methods.
using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

[Authorize] //Requires authentication for the entire controller

public class HomeController : Controller

public string NonSecureMethod()

return "This method can be accessed by everyone as it is non-secure method";

}
467

public string SecureMethod()

return "This method needs to be access by authorized users as it SecureMethod";

public string Login()

return "This is the Login Page";

}
Now, suppose you want to allow anonymous access to the NonSecureMethod and Login
method of the Home Controller. In that case, you need to decorate the NonSecureMethod
and Login method with the AllowAnonymous attribute, as shown below. The
AllowAnonymous attribute in ASP.NET Core MVC is used to skip the authorization enforced
by the Authorization Filter.
using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

[Authorize] //Requires authentication for the entire controller

public class HomeController : Controller

public string SecureMethod()

return "This method needs to be access by authorized users as it SecureMethod";

}
468

[AllowAnonymous]

public string NonSecureMethod()

return "This method can be accessed by everyone as it is non-secure method";

[AllowAnonymous]

public string Login()

return "This is the Login Page";

}
Note: If both Authorize and AllowAnonymous Attributes are applied to an action method,
then the AllowAnonymous attribute will take priority and be accessed by any user.
Custom Authorization Filter in ASP.NET Core:
We can also create Custom Authentication filters. To create Custom Authentication in
ASP.NET Core MVC, we need to create a class implementing
the IAuthorizationFilter or IAsyncAuthorizationFilter interface and provide
implementations for the OnAuthorization method, where you need to write the custom
authentication logic as per our business requirements.
Example to Understand Custom Authorization Filter in ASP.NET Core
In ASP.NET Core, we can create a Custom Authorization Filter to redirect the User to the
login page when the user is not authenticated. Let us see how we can create a custom
authorization filter to achieve this:
Create a class file named CustomAuthorizationFilterAttribute.cs, and then copy and
paste the following code. As you can see, the following class is inherited from the Attribute
and IAuthorizationFilter classes so that we can use this class as an Attribute. This class
implements the IAuthorizationFilter interface and provides the implementation for
the OnAuthorization method, where we have written our custom authorization logic.
using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

using System.Security.Claims;
469

namespace FiltersDemo.Models

public class CustomAuthorizationFilterAttribute : Attribute, IAuthorizationFilter

public void OnAuthorization(AuthorizationFilterContext context)

// Your custom authorization logic here

if (!IsAuthorized(context.HttpContext.User))

// If not authenticated, redirect to the login page

context.Result = new RedirectToRouteResult(new RouteValueDictionary

{ "controller", "Home" }, // Change "Home" to your login controller name

{ "action", "Login" } // Change "Login" to your login action method name

});

private bool IsAuthorized(ClaimsPrincipal user)

// Check if the user is authenticated

// Implement your custom authorization logic here

// Check roles, claims, policies, or any other criteria

// Return true if authorized, false if not

return false; // For demonstration purposes


470

}
Now, we need to decorate the controller or action method where we need to implement our
custom authorization logic. Let us decorate the SecureMethod with our Custom
Authorization Filter Attribute. So, modify the Home Controller as follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[CustomAuthorizationFilterAttribute] // Apply the custom


CustomAuthorizationFilterAttribute

public string SecureMethod()

return "This method needs to be access by authorized users as it SecureMethod";

[AllowAnonymous]

public string NonSecureMethod()

return "This method can be accessed by everyone as it is non-secure method";

[AllowAnonymous]
471

public string Login()

return "This is the Login Page";

}
With the above changes in place, visit the SecureMethod, and you will see that it will
redirect to the Login Page.
Creating a Custom Authorization Filter
In the previous example, the CustomAuthorizationFilterAttribute class is inherited from
the Attribute class, and that’s why we apply
the CustomAuthorizationFilterAttribute directly to the action methods and controller. Let
us remove the inherited Attribute class from the CustomAuthorizationFilterAttribute class
definition, and let’s inherit from the IAuthorizationFilter interface. So, modify the
CustomAuthorizationFilterAttribute class as follows:
using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

using System.Security.Claims;

namespace FiltersDemo.Models

public class CustomAuthorizationFilter : IAuthorizationFilter

public void OnAuthorization(AuthorizationFilterContext context)

// Your custom authorization logic here

if (!IsAuthorized(context.HttpContext.User))

// If not authenticated, redirect to the login page


472

context.Result = new RedirectToRouteResult(new RouteValueDictionary

{ "controller", "Home" }, // Change "Account" to your login controller name

{ "action", "Login" } // Change "Login" to your login action method name

});

private bool IsAuthorized(ClaimsPrincipal user)

// Check if the user is authenticated

// Implement your custom authorization logic here

// Check roles, claims, policies, or any other criteria

// Return true if authorized, false if not

return false; // For demonstration purposes

}
Register the Filter Globally, on a Controller, or an Action:
Now, we can register the Custom Authorization Filter at 3 different places. We can register
the filter globally, which means it will be applied to all controllers and actions in your
application. To do so, you need to modify the MVC Service registration in your Program.cs
class file as follows.
builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomAuthorizationFilter());

});
473

Using Authentication Filter at Controller and Action Method Level:


You can also apply the Custom Authorization Filter to a specific controller or action method
using the built-in ServiceFilter and TypeFilter Attribute. We use the TypeFilter attribute in
the code below to specify the CustomAuthorizationFilter.
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[TypeFilter(typeof(CustomAuthorizationFilter))]

public string Index()

return "String Data from Index Action Method";

}
Custom Asynchronous Authorization Filter in ASP.NET Core MVC
It is also possible to create the Custom Authorization Filter asynchronous. For this, our
Custom Authorization Filter class needs to implement the IAsyncAuthorizationFilter
interface and provide the implementation for the OnAuthorizationAsync method. This type of
filter is suitable for performing asynchronous operations, such as database calls or any I/O-
bound work, within your authorization logic.
Create the Custom Asynchronous Authorization Filter:
So, create a class file named CustomAsyncAuthorizationFilter.cs and copy and paste the
following code. Here, you can see the CustomAsyncAuthorizationFilter class implement the
IAsyncAuthorizationFilter interface and provide implementations for the
OnAuthorizationAsync method where we have written the custom Authorization Logic:
using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models
474

public class CustomAsyncAuthorizationFilter : IAsyncAuthorizationFilter

public async Task OnAuthorizationAsync(AuthorizationFilterContext context)

// Your asynchronous custom authorization logic here

bool isAuthorized = await CheckUserAuthorizationAsync(context);

if (!isAuthorized)

// If not authenticated, redirect to the login page

context.Result = new RedirectToRouteResult(new RouteValueDictionary

{ "controller", "Home" }, // Change "Account" to your login controller name

{ "action", "Login" } // Change "Login" to your login action method name

});

private async Task<bool> CheckUserAuthorizationAsync(AuthorizationFilterContext


context)

// Implement your asynchronous authorization logic here

// For example, you can check user permissions, roles, etc., using async calls

await Task.Delay(1000); // Simulate async work, like a database call

// Return true if authorized, false otherwise


475

return false;

}
Register the Filter:
You can register the filter globally or on specific controllers or actions:
Globally:
builder.Services.AddControllersWithViews(options =>

options.Filters.Add(new CustomAsyncAuthorizationFilter());

});
On a Controller or Action:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

[TypeFilter(typeof(CustomAsyncAuthorizationFilter))]

public class SomeController : Controller

// All actions in this controller will use the CustomAsyncAuthorizationFilter

public class AnotherController : Controller

[TypeFilter(typeof(CustomAsyncAuthorizationFilter))]

public async Task<IActionResult> SomeAction()

{
476

// This action will use the CustomAsyncAuthorizationFilter

return View();

}
Note: Once we discuss the ASP.NET Core Identity, you will see the real implementation
with proper roles, claims, etc.
When Should We Use Authorization Filter in ASP.NET Core MVC?
Authorization filters in ASP.NET Core MVC should be used when you need to control
access to specific controllers or actions within your web application based on authentication
and authorization rules. The following are some common scenarios in which you should use
authorization filters:
 Securing Sensitive Data or Functionality: Use authorization filters to
protect endpoints that access sensitive data or perform sensitive operations,
ensuring that only authorized users can access them.
 Role-Based Access Control: When you have different user roles in your
application (like admin, user, and moderator) and want to restrict access to
certain actions or controllers based on these roles.
 Error Handling: Authorization filters can also handle unauthorized access by
redirecting users to login pages, displaying access-denied messages, or
taking other appropriate actions.

Action Filters in ASP.NET Core MVC


In this article, I will discuss Action Filters in ASP.NET Core MVC Applications with
Examples. Please read our previous article discussing Authorization Filters in ASP.NET
Core MVC Applications.
What are Action Filters in ASP.NET Core MVC?
In ASP.NET Core MVC, action filters allow us to execute code before or after the execution
of action methods in controllers. Action Filters are useful for handling cross-cutting concerns
within an application, such as Logging, Authentication, Authorization, Caching, Exception
Handling, etc.
How Do We Create a Custom Action Filter in ASP.NET Core MVC?
In ASP.NET Core, we can create a Custom Action Filter in two ways: First, by creating a
class implementing the IActionFilter interface and providing implementations
for [OnActionExecuting] and [OnActionExecuted] methods. Second, by creating a class
inheriting from the ActionFilterAttribute class and overriding
the [OnActionExecuting] and [OnActionExecuted] methods.
 OnActionExecuting: This method is called before the action method is
executed
477

 OnActionExecuted: This method is called after the action method executes


but before the result is processed.
Note: If you implement the IAsyncActionFilter interface, you need to provide
implementations for the OnActionExecutionAsync method.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:16 / 03:1710 Sec

Real-Time Example to Understand Action Filters in ASP.NET Core MVC:


Let us create one Real-Time Application to understand the need and use of Custom Action
Filters in ASP.NET Core MVC Application. We are going to develop one application where
we will implement the following functionalities:
 Logging: Implementing logging of action method calls, parameters,
execution times, etc.
 Data Transformation: Modifying the data passed to an action or returned
from an action.
 Validation: Performing custom validation of action parameters or the request
itself.
 Error Handling: Implementing custom error handling logic for actions.
 Caching: Implementing custom caching strategies for action results.
For each functionality, we will create a separate Custom Action Filter. I will also implement
the Custom Action filter in different ways. I will also show you how to use Custom Services
within the Custom Action filter. Also, I will discuss How to register the Custom Action Filter
at different levels, i.e., Globally, Controller level and action method level. Finally, I will create
one Controller with action methods showing the use of each Custom Action Filter:
Creating a Model:
Create a class file named MyCustomModel.cs within the Models folder and then copy and
paste the following code into it. This will be our model, which we will use to return the data
to the client and the action method parameter.
namespace FiltersDemo.Models

public class MyCustomModel

public string? Name { get; set; }

public string? Address { get; set; }

public void TransformData()

Name += " - Transformed";


478

Address += " - Transformed";

}
We will see how to Validate the above Model using a Custom Action Filter as well as we will
see how to modify the above model data using a Custom Action Filter while returning the
data to the client.
Creating a Logger Service:
Let us define our service interface and implementation for the logging. This Logger service
is going to be used by our Custom Action Filters. So, create an interface
named ILoggerService.cs within the Models folder and then copy and paste the following
code:
namespace FiltersDemo.Models

public interface ILoggerService

public void Log(string message);

}
Next, create another class file named LoggerService.cs within the Models folder and copy
and paste the following code. This class implements the ILoggerService interface and
implements the Log method, where we have written the logic to store the log message in a
text file.
namespace FiltersDemo.Models

public class LoggerService : ILoggerService

public void Log(string message)

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");


479

//saving the data in a text file called Log.txt within the Log folder which must be

//created at the Project root directory

File.AppendAllText(filePath, message);

}
Creating the Log Folder:
Next, create a folder called Log within the Project root directory where the Log.txt file is
going to be generated by the application.
Registering the Logger Service:
Next, we need to register the Logger Service into the built-in dependency injection
container. This is because we want to use the Logger service through our application,
including the Custom Action Filter, and we want the Framework to inject the logger service
through the constructor. So, add the following code to the Program.cs class file:
builder.Services.AddSingleton<ILoggerService, LoggerService>();
Creating a Custom Action Filter for Logging in ASP.NET Core MVC:
Create a class file named LoggingFilterAttribute.cs within the Models folder, and then
copy and paste the following code. This filter logs details about the action method calls,
including parameters, execution times, etc.
using Microsoft.AspNetCore.Mvc.Filters;

using Newtonsoft.Json;

using System.Diagnostics;

namespace FiltersDemo.Models

public class LoggingFilterAttribute : ActionFilterAttribute

private Stopwatch? _timer = null;

private readonly ILoggerService _LoggerService;

public LoggingFilterAttribute(ILoggerService LoggerService)

{
480

_LoggerService = LoggerService;

public override void OnActionExecuting(ActionExecutingContext context)

_timer = Stopwatch.StartNew();

var actionName = context.ActionDescriptor.RouteValues["action"];

var controllerName = context.ActionDescriptor.RouteValues["controller"];

var parameters = JsonConvert.SerializeObject(context.ActionArguments);

string message = $"Starting {controllerName}.{actionName} with parameters


{parameters}";

_LoggerService.Log(message);

base.OnActionExecuting(context);

public override void OnActionExecuted(ActionExecutedContext context)

_timer?.Stop();

var actionName = context.ActionDescriptor.RouteValues["action"];

var controllerName = context.ActionDescriptor.RouteValues["controller"];

string message = $"Finished {controllerName}.{actionName} in


{_timer.ElapsedMilliseconds}ms";

_LoggerService.Log(message);

base.OnActionExecuted(context);

}
481

}
The above LoggingFilterAttribute is a Custom Filter that inherits from ActionFilterAttribute.
This class is used to log information about MVC actions, specifically when they start and
when they finish. It uses two methods, OnActionExecuting and OnActionExecuted, to
achieve this.
OnActionExecuting Method
The OnActionExecuting method is called before the action method is executed. This
method is used for the following purposes:
 Start a Timer: It initializes and starts the Stopwatch to measure the duration
of the action’s execution. This is important for logging the action’s execution
time.
 Preparing the Log Message: It constructs a message that includes the
controller’s name, the action’s name, and the action’s serialized arguments.
 Log the Start of the Action: The constructed message (indicating the start
of action execution) is logged using the _LoggerService. This helps in
tracking when an action begins its execution.
OnActionExecuted Method
The OnActionExecuted method is invoked after the action method has been executed. This
method is used for the following purposes:
 Stop the Timer: It stops the Stopwatch that was started in
OnActionExecuting.
 Preparing the Log Message: Similar to OnActionExecuting, it constructs a
message that includes the name of the controller, the action, and the time
taken (in milliseconds) to execute the action.
 Logging the End of the Action: This message (indicating the completion of
the action and its duration) is then logged using the same logging service.
Note: Both methods call base.OnActionExecuting(context) and
base.OnActionExecuted(context) at the end of their implementations. These calls ensure
that any additional processing defined in the base class (ActionFilterAttribute) is also
executed.
Creating a Custom Action Filter for Data Transformation in ASP.NET
Core MVC:
Create a class file named DataTransformationFilterAttribute.cs within the Models folder,
and then copy and paste the following code. This filter modifies the data returned from an
action method. It assumes you are returning a specific model that could be transformed.
Here, the following class is inherited from the ActionFilterAttribute and overrides the
OnActionExecuted method.
using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Models

public class DataTransformationFilterAttribute : ActionFilterAttribute


482

public override void OnActionExecuted(ActionExecutedContext context)

if (context.Result is ViewResult viewResult)

if (viewResult.Model is MyCustomModel model)

// Directly modify the model data

model.TransformData();

base.OnActionExecuted(context);

}
Understanding the OnActionExecuted method:
 Check the Result Type: The method first checks if the action method’s result
(context.Result) is of type ViewResult. ViewResult is a type of action result
that renders a view as the response to the request.
 Access and Modify the Model: If the result type is ViewResult, the method
then checks if the model associated with this view result is of type
MyCustomModel. If it is, it accesses this model.
 Transform the Model Data: Once it has access to the model, the method
calls the TransformData() function on the model. This function is assumed to
manipulate or transform the data within the model. Depending on the
implementation of TransformData(), this could involve changing values,
formatting data, calculating fields, etc.
 Call Base Method: Finally, the method calls
base.OnActionExecuted(context), which ensures that any logic implemented
in the base class’s OnActionExecuted method is also executed.
Creating a Custom Action Filter for Validation in ASP.NET Core MVC:
483

Create a class file named CustomValidationFilter.cs within the Models folder, and then
copy and paste the following code. This filter performs custom validation of action
parameters. Here, we created the Custom Action Filter, implementing
the IActionFilter interface and providing implementations for
the OnActionExecuting and OnActionExecuted methods. Actually, we are not doing
anything on the OnActionExecuted methods.
using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.ModelBinding;

using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace FiltersDemo.Models

public class CustomValidationFilter : IActionFilter

public void OnActionExecuting(ActionExecutingContext context)

if (context.ActionArguments.TryGetValue("model", out var value) && value is


MyCustomModel model)

// Validate Name

if (string.IsNullOrWhiteSpace(model.Name))

context.ModelState.AddModelError(nameof(model.Name), "Name cannot be empty or


whitespace.");

// Validate Address

if (string.IsNullOrWhiteSpace(model.Address))
484

context.ModelState.AddModelError(nameof(model.Address), "Address cannot be empty or


whitespace.");

if (!context.ModelState.IsValid)

// Assuming the controller action expects a return of the same view with the model

context.Result = new ViewResult

ViewName = context.RouteData.Values["action"].ToString(), // Gets the action name

ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(),


context.ModelState)

Model = context.ActionArguments.FirstOrDefault(arg => arg.Value is


MyCustomModel).Value

};

public void OnActionExecuted(ActionExecutedContext context)

// Custom logic after the action executes

// For this filter, we do nothing here, but you could add post-processing logic if needed.

}
485

}
Understanding OnActionExecuting Method
 Argument Validation: The method starts by attempting to retrieve an
argument named “model” from the context’s ActionArguments. It then checks
if this argument is of type MyCustomModel.
 Name Validation: If the model’s Name property is null or whitespace, a
model error stating that “Name cannot be empty or whitespace” is added.
 Address Validation: Similarly, it checks the Address property and adds a
model error if it’s null or whitespace.
 Invalid Model Handling: If the model state is invalid, it prevents the
execution of the action method by setting the context’s Result property.
 Creating and Returning ViewResult: A new ViewResult is created to return
the same view with the invalid model data. This ViewResult includes the
name of the action (retrieved from context.RouteData) to indicate which view
to return. A new ViewDataDictionary is initialized with the model state and
model, allowing the view to display validation errors.
Creating a Custom Action Filter for Error Handling in ASP.NET Core
MVC:
Create a class file named ErrorHandlerFilterAttribute.cs within the Models folder, and
then copy and paste the following code. This filter implements custom error-handling logic
for actions. The Custom Action Filter is inherited from the ExceptionFilterAttribute class and
overrides the OnException method.
using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.ModelBinding;

using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace FiltersDemo.Models

public class ErrorHandlerFilterAttribute : ExceptionFilterAttribute

private readonly ILoggerService _LoggerService;

public ErrorHandlerFilterAttribute(ILoggerService LoggerService)

{
486

_LoggerService = LoggerService;

public override void OnException(ExceptionContext context)

string message = $"An error occurred in {context.ActionDescriptor.DisplayName}:


{context.Exception}";

_LoggerService.Log(message);

// Set the result to redirect to the generic error view

context.Result = new ViewResult

ViewName = "~/Views/Shared/Error.cshtml", // Explicit path to the view

ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(),


context.ModelState)

{"Exception", context.Exception} // Optionally pass exception data to the view

};

context.ExceptionHandled = true; // Mark exception as handled

}
Understanding OnException Method:
 Logging the Exception: When an exception occurs in any action method to
which this filter is applied, the OnException method captures the exception
and logs a detailed message. The message includes the name of the action
where the exception occurred and the exception details itself. This is done by
the Logger Service, which is injected through the constructor.
487

 Setting the Response: After logging the exception, the method proceeds to
alter the user’s experience by redirecting them to a generic error page. This is
done by setting context.Result to a new ViewResult:
 View Name: It specifies the path to the error view
(~/Views/Shared/Error.cshtml). This ensures that whenever an exception is
handled by this filter, the user is redirected to a standard error page,
maintaining a consistent error-handling strategy across the application.
 ViewData: The ViewDataDictionary is populated with the exception data
using the EmptyModelMetadataProvider and the current ModelState. This
allows the error view to access details of the exception, if necessary, which
can be useful for displaying error messages or diagnostic information on the
error page.
 Marking the Exception as Handled: By setting context.ExceptionHandled to
true, the filter communicates to the ASP.NET Core framework that the
exception has been handled. This prevents the exception from propagating
further, which means it won’t trigger other exception handlers or result in the
framework’s default error-handling mechanisms taking over (such as showing
the developer exception page).
Creating the Error View:
Next, create a view named Error.cshtml within the Views/Shared folder and then copy and
paste the following code. This is the view which is going to be rendered by the above
Custom Error Handler:
@{

Layout = "~/Views/Shared/_Layout.cshtml";

ViewData["Title"] = "Error";

<h1 class="text-danger">Oops! Something went wrong.</h1>

<p>We're having trouble processing your request. Please try again later.</p>
Creating a Custom Action Filter for Caching in ASP.NET Core MVC:
Create a class file named AsyncCachingFilter.cs within the Models folder, and then copy
and paste the following code. The following Custom Action Filter implements the
IAsyncActionFilter and provides an implementation for the OnActionExecutionAsync
method.
using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Caching.Memory;

namespace FiltersDemo.Models
488

public class AsyncCachingFilter : IAsyncActionFilter

private readonly IMemoryCache _cache;

private readonly TimeSpan _expirationTimeSpan;

public AsyncCachingFilter(IMemoryCache cache, double secondsToCache = 60)

_cache = cache;

_expirationTimeSpan = TimeSpan.FromSeconds(secondsToCache);

public async Task OnActionExecutionAsync(ActionExecutingContext context,


ActionExecutionDelegate next)

var key = $"{context.HttpContext.Request.Path}";

if (_cache.TryGetValue(key, out IActionResult cachedResult))

context.Result = cachedResult; // Return cached result

else

// Proceed with the action execution

var executedContext = await next();

// Cache any IActionResult that is successfully returned

if (executedContext.Result is ActionResult actionResult)


489

_cache.Set(key, actionResult, _expirationTimeSpan);

}
Understanding OnActionExecutionAsync Method:
 Initialization: The Constructor uses an injected IMemoryCache instance
(_cache) to store and retrieve cached data. The cache’s expiration is set via a
TimeSpan (_expirationTimeSpan), which is determined by the number of
seconds passed into the constructor.
 Caching Logic: The cache key is generated from the request path
(context.HttpContext.Request.Path). This ensures that each unique URL has
its own cache entry.
 Cache Lookup: The method first checks if there is a cached result available
for the generated key using _cache.TryGetValue(key, out IActionResult
cachedResult). If a cached result exists (cachedResult), it is immediately
assigned to context.Result. This tells the framework to skip executing the
action method and return the cached result directly to the client.
 Action Execution: If no cached result is found, the method proceeds to
execute the action by calling await next(). This ActionExecutionDelegate
(next) represents the next delegate in the action filter pipeline, which typically
leads to the execution of the action method itself. After the action executes,
the resulting context (executedContext) is returned, which includes the result
of the action method.
 Caching the Result: The result of the action method
(executedContext.Result) is checked to see if it’s an instance of ActionResult.
This check is important because only action results should be cached. If it is
an ActionResult, it is added to the cache with the earlier generated key. The
cache entry is set to expire based on _expirationTimeSpan.
Modifying the Home Controller:
Let’s modify the Home Controller class code as follows to demonstrate the use of all
created Custom Action Filters.
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers
490

[TypeFilter(typeof(LoggingFilterAttribute))]

public class HomeController : Controller

[TypeFilter(typeof(AsyncCachingFilter))]

public IActionResult Index()

// Storing the current time in ViewBag

ViewBag.CurrentTime = DateTime.Now;

return View();

[DataTransformationFilter]

public IActionResult Details()

var model = new MyCustomModel

Name = "Initial Name",

Address = "Initial Address"

};

//return Ok(model);

return View(model);

[HttpGet]

public IActionResult Create()


491

return View();

[TypeFilter(typeof(CustomValidationFilter))]

[HttpPost]

public IActionResult Create(MyCustomModel model)

if (ModelState.IsValid)

// Process the valid model here

return RedirectToAction(nameof(Index));

return View(model);

[TypeFilter(typeof(ErrorHandlerFilterAttribute))]

public IActionResult Error()

throw new Exception("This is a forced error!");

}
Creating and Modifying the Views:
Next, we need to modify and create the Views as per our requirements:
Index.cshtml View:
Modify the Index.cshtml View as follows:
@{
492

ViewData["Title"] = "Index Page";

<h2>Current Server Time</h2>

<p>The current server time is: @ViewBag.CurrentTime.ToString("F")</p>


Details.cshtml View
Add Details.cshtml View within the Views/Home folder and then copy and paste the
following code:
@model FiltersDemo.Models.MyCustomModel

@{

ViewData["Title"] = "Details Page";

<div class="container mt-5">

<h1 class="mb-3">Details</h1>

<div class="card">

<div class="card-body">

<h5 class="card-title">Name</h5>

<p class="card-text">@Model.Name</p>

<h5 class="card-title">Address</h5>

<p class="card-text">@Model.Address</p>

</div>

</div>

</div>
Create.cshtml View
Add Create.cshtml View within the Views/Home folder and then copy and paste the
following code:
@model FiltersDemo.Models.MyCustomModel

@{
493

ViewData["Title"] = "Create Model";

<div class="container mt-5">

<h1>Create Model</h1>

<form asp-action="Create" method="post" class="needs-validation" novalidate>

<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>

<div class="form-group">

<label for="Name">Name</label>

<input asp-for="Name" class="form-control" />

<span asp-validation-for="Name" class="text-danger"></span>

</div>

<div class="form-group">

<label for="Address">Address</label>

<input asp-for="Address" class="form-control" />

<span asp-validation-for="Address" class="text-danger"></span>

</div>

<button type="submit" class="btn btn-primary">Submit</button>

</form>

</div>
Testing the Application:
Now, run the application and test each action method. We have applied the Logging
Custom Filter at the Controller level, meaning it will be applied to all Action methods of the
Home Controller.
Index Action Method:
Now, access the Home/Index URL, and you should see the following. Now, within 60
seconds, if you access the same page, then you will see that the Date is not going to be
changed. This is because we have applied the Custom Cache filter on the Index Action
method:
494

Details Action Method:


Now, access the Home/Details URL, and you should see the following. This is because we
have applied the Custom Data Modification filter on the Details Action method, which
modifies the Model:

Create Action Method:


Now, access the Home/Create URL without providing any data, click on the Submit button,
and you should see the following. This is because we have applied the Custom Validator
filter on the Create Post Action method, which is doing the Model validation before
executing the Create Post action method:
495

Error Action Method:


Now, access the Home/Error URL, and you should see the following. This is because we
have applied the Custom Exception filter to the Error Action method. The Error action
method throws an unhandled exception that is going to be handled by the Custom
Exception Filter, and then it returns a generic error page to the client.

TypeFilter vs ServiceFilter in ASP.NET Core MVC


In this article, I will discuss TypeFilter vs ServiceFilter, i.e., the Difference Between
TypeFilter and ServiceFilter in ASP.NET Core MVC Applications with Examples. Please
read our previous article discussing Action Filters in ASP.NET Core MVC Applications.
TypeFilter vs ServiceFilter in ASP.NET Core MVC
496

In ASP.NET Core, Filters execute custom pre- and post-processing logic before and after
the action method execution. They can be used for various purposes, such as
Authentication, Authorization, Caching, Exception Handling, Result Modification, etc. There
are different kinds of filters (like Authorization, Action, Exception, and Result filters). We can
also apply the filters to controllers and actions in different ways, such as applying at the
action method level, at the controller level, or globally.
In ASP.NET Core, TypeFilter and ServiceFilter are built-in attributes that apply custom
filters to controller actions or controllers. However, they have different purposes and are
used in different scenarios. Now, let us proceed and try to understand the TypeFilter and
ServiceFilter, what are the differences between them, and when to use which one:
TypeFilter Attribute in ASP.NET Core MVC:
TypeFilter is a Built-in Attribute in ASP.NET Core that allows us to apply a Custom Filter to
a single action or controller. We must provide the filter type (Custom Filter Class name) as a
parameter to the TypeFilter attribute. It is useful when we want to apply a Custom Filter
Class to a specific action or controller and don’t need to configure it globally or inject
additional services.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:08 / 03:1710 Sec

Example to Understand TypeFilter Attribute in ASP.NET Core MVC:


Let us see an example of understanding the TypeFilter Attribute in an ASP.NET Core MVC
Application. Remember that you need to use the TypeFilter or Service Filter Attribute only
when your Custom Filter class requires some services and you want to inject such services
using the Dependency Injection Container.
So, let us first create a service that is going to be consumed by the Custom Filter Classes.
So, create a class file named ILoggerService.cs and copy and paste the following code.
Here, you can see that the ILoggerService interface defines one method called Log, and the
LoggerService class implements the ILoggerService interface and provides the
implementation for the Log method.
namespace FiltersDemo.Models

public interface ILoggerService

public void Log(string methodName, string message);

public class LoggerService : ILoggerService

public void Log(string methodName, string message)


497

string filePath = Path.Combine(Directory.GetCurrentDirectory(), "Log", "Log.txt");

//saving the data in a text file called Log.txt

File.AppendAllText(filePath, message);

}
Creating Custom Action Filter:
Next, create a class file named CustomLoggingFilter.cs and then copy and paste the
following code. As you can see, this class implements the IActionFilter interface and
provides implementations for the OnActionExecuting and OnActionExecuted methods.
Further, we have injected the LoggerService instance through constructor dependency
injection.
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersDemo.Models

public class CustomLoggingFilter : IActionFilter

private readonly ILoggerService _LoggerService;

public CustomLoggingFilter(ILoggerService LoggerService)

_LoggerService = LoggerService;

public void OnActionExecuting(ActionExecutingContext context)

var controllerName = context.RouteData.Values["controller"];

var actionName = context.RouteData.Values["action"];


498

string message = " Controller:" + controllerName + " Action:" + actionName + " Date: "

+ DateTime.Now.ToString() + Environment.NewLine;

// Log the information before the action executes.

_LoggerService.Log("OnActionExecuting", message);

public void OnActionExecuted(ActionExecutedContext context)

var controllerName = context.RouteData.Values["controller"];

var actionName = context.RouteData.Values["action"];

string message = " Controller:" + controllerName + " Action:" + actionName + " Date: "

+ DateTime.Now.ToString() + Environment.NewLine;

// Log the information after the action executes.

_LoggerService.Log("OnActionExecuted", message);

}
Registering the Services to the Dependency Injection Container:
Next, we must register the Services to the Dependency Injection Container within the
Program class so that the MVC Framework injects the service. So, add the following line to
the Program class. Here, it is not required to register the Custom Filter into the dependency
injection container.
builder.Services.AddScoped<ILoggerService, LoggerService>();
Apply Filter as a Service:
To apply the filter as a service, we can use TypeFilter Attribute. A TypeFilterAttribute is
used to create an instance of a Custom Filter Class using dependency injection. You need
to pass the Custom Filter Type as an argument to its constructor. The Type Filter
instantiates the filter type each time the filter is applied. So, modify the Home Controller as
follows:
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;
499

namespace FiltersDemo.Controllers

public class HomeController : Controller

[TypeFilter(typeof(CustomLoggingFilter))]

public ActionResult Index()

return View();

[TypeFilter(typeof(CustomLoggingFilter))]

public ActionResult Details()

return View();

}
Modify Index.cshtml File
@{

ViewData["Title"] = "Index Page";

<h2>Index Page</h2>
Add Details.cshtml File
@{

ViewData["Title"] = "Details Page";

}
500

<h2>Details Page</h2>
Testing the Application:
Each time a request is made to either the Index or Details action methods, a new instance
of CustomLoggingFilter is created. This is because the [TypeFilter] attribute creates an
instance of the specified filter type per request for each action method it decorates.
Therefore, if we make a request to the Index method and another request to the Details
method, two separate instances of CustomLoggingFilter will be created, i.e., one for each
request. Each subsequent request to these methods will create new instances accordingly.
Now, run the application, access both Index and Details action methods, and then check
the Log.txt file; you should see that the Instance of the CustomLoggingFilter was created
twice, as shown in the image below.

ServiceFilter Attribute in ASP.NET Core MVC:


ServiceFilter is also a Built-in Attribute in ASP.NET Core that applies a Custom Filter as an
Attribute in the Controller or Controller Action Method. However, the Custom Filter must be
registered as a service in the dependency injection container. It is useful when we want to
reuse the same Custom Filter Instance across multiple actions or controllers. That means,
unlike the TypeFilter, it doesn’t create a new instance but retrieves it from the DI container.
We are going to work with the same example. So, modify the Home Controller as follows to
use the ServiceFilter Attribute. In both action methods, you can see that we have applied
the CustomLoggingFilter using the ServiceFilter attribute.
using FiltersDemo.Models;

using Microsoft.AspNetCore.Mvc;

namespace FiltersDemo.Controllers

public class HomeController : Controller

[ServiceFilter(typeof(CustomLoggingFilter))]

public ActionResult Index()

{
501

return View();

[ServiceFilter(typeof(CustomLoggingFilter))]

public ActionResult Details()

return View();

}
At this point, if you run the application, then you will get the following 500 HTTP Error. This
is because we are applying the CustomLoggingFilter using the ServiceFilter attribute, but
we have not yet registered the CustomLoggingFilter as a service into the built-in
dependency injection container.

Registering the Custom Filter as a Service:


With TypeFilter, we specify the filter type directly within the TypeFilter constructor and don’t
need to register it as a service into the Dependency Injection Container. But, with
ServiceFilter, we must register the Custom Filter as a service into the Dependency Injection
Container before using it with the ServiceFilter attribute. So, add the following code to the
Program class.
builder.Services.AddSingleton<CustomLoggingFilter>();
With the above changes, run the application and access both the Index and Details action
methods. Then verify the Log.txt file, and this time, you will see that only one instance of
the CustomLoggingFilter is created, as shown in the image below. This is because we
registered the CustomLoggingFilter using the AddSingleton method, which, as we already
502

discussed, will create only one instance of the registered service throughout the
application’s life and rescue that instance.

Differences Between TypeFilter and ServiceFilter in ASP.NET Core:


The following are the Differences Between TypeFilter and ServiceFilter in ASP.NET Core
MVC:
 TypeFilter creates a new instance of the filter type on each request. On the
other hand, the ServiceFilter retrieves the instance from the DI container,
which could be a singleton, scoped, or transient instance, depending on how
it’s registered.
 The TypeFilter is more flexible because it can instantiate types that aren’t
registered in the DI container, while the ServiceFilter requires the type to be
registered in the DI container.
So, we need to use TypeFilter if we need to inject dependencies that aren’t registered in the
container. We need to use ServiceFilter if the filter is already registered as a service in the
container, and we want the Dependency Injection Container to manage the filter’s lifetime.

Cross-Site Request Forgery and AntiForgeryToken in ASP.NET Core MVC


In this article, I will discuss Cross-Site Request Forgery and AntiForgery Token in
ASP.NET Core MVC Applications with Examples. Please read our previous article
discussing the Difference Between TypeFilter and ServiceFilter in ASP.NET Core MVC.
In this article, first, I will discuss What is Cross-Site Request Forgery (CSRF or XSRF) with
an example, and then I will show you how we can prevent the CSRF or XSRF attack using
AntiForgery Token in ASP.NET Core MVC Applications.
What is a Cross-Site Request Forgery Attack?
Cross-Site Request Forgery (CSRF), also known as XSRF or Sea Surf, is a type of security
attack that exploits the trust that a web application has in an authenticated user’s browser.
This allows an attacker to perform actions on behalf of the authenticated user without their
knowledge or consent. In a CSRF attack, the attacker tricks the victim into submitting a
malicious request to a web application where they are authenticated, exploiting the user’s
trust. If this is not clear at the moment, don’t worry; we will try to understand it with an
example.
Example to Understand Cross-Site Request Forgery (CSRF or XSRF)
Attack
Let us create a new ASP.NET Core Application using the Model-View-Controller Project
template and give the project name BankingApplication. Once you create the project,
modify the Home Controller as follows. So, our requirement is to create a view for updating
the PIN of an Account Number of a user. In the below code, the Get version of the
ChangePin action method will create a form where the user will enter his Account Number
and Updated PIN and click on the PIN Change button. Once the User clicks on the PIN
Change button, the form data will be submitted to the Post version of the ChangePin action
503

method, which will process the data, update the PIN number, and then redirect to the
PinChangeSuccess page.
using Microsoft.AspNetCore.Mvc;

namespace BankingApplication.Controllers

public class HomeController : Controller

[HttpGet]

public IActionResult ChangePin()

return View();

[HttpPost]

public ActionResult ChangePin(string AccountNumber, string Pin)

// Process the data

TempData["Message"]= $"AccountNumber: {AccountNumber} Pin Changed to: {Pin}";

return RedirectToAction("PinChangeSuccess");

public ActionResult PinChangeSuccess()

return View();

}
504

Creating the ChangePin View:


Next, create a view named ChangePin.cshtml within the Views/Home folder and then
copy and paste the following code. Here, you can see we have one form with two text boxes
and one submit button.
Ad
1/2
00:17

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV

@{

ViewData["Title"] = "Pin Change Page";

<div>

<form asp-controller="Home" asp-action="ChangePin" method="post" class="mt-3">

<div style="margin-top:7px" class="form-group row">

<label for="AccountNumber" class="col-sm-2 col-form-label">Account Number</label>

<div class="col-sm-10">

<input type="text" name="AccountNumber" id="AccountNumber" class="form-control" />

</div>

</div>

<div style="margin-top:7px" class="form-group row">

<label for="Pin" class="col-sm-2 col-form-label">Pin</label>

<div class="col-sm-10">

<input type="text" name="Pin" id="Pin" class="form-control" />

</div>

</div>

<div style="margin-top:10px" class="form-group row">


505

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">PIN Change</button>

</div>

</div>

</form>

</div>
Creating the PinChangeSuccess View:
Next, create a view named PinChangeSuccess.cshtml within the Views/Home folder and
then copy and paste the following code. We are simply displaying the PIN Change Success
message.
@{

ViewData["Title"] = "PIN Change Success";

<div class="container">

<div class="row justify-content-center">

<div class="col-md-8">

<div class="alert alert-success mt-5" role="alert">

<h4 class="alert-heading">Success!</h4>

<p>@TempData["Message"]</p>

<hr>

<p class="mb-0">If you need to make any further changes, please return to your account
settings.</p>

</div>

</div>

</div>

</div>
506

Now, run the application, visit the Home/ChangePin, enter the Account Number and PIN,
and then click on the PIN Change button as shown in the below image:

Once you click on the PIN Change button, the PIN has been changed for the account
number, and you should see the following success message:

As you can see, the application is working as expected. Please observe the
URL https://localhost:7199/Home/ChangePin. Now, let’s see how a hacker can use the
above URL to launch a Cross-Site Request Forgery (CSRF or XSRF) attack.
Hackers Application:
Let us create a new ASP.NET Core Application using the Model View Controller Project
template and give the project name HackerApplication. Once you create the
HackerApplication, then modify the Home Controller as follows:
using Microsoft.AspNetCore.Mvc;
507

namespace HackerApplication.Controllers

public class HomeController : Controller

public IActionResult Index()

return View();

}
Next, modify the Index.cshtml view of the Home Controller of HackerApplication as follows.
Here, you can see we are posting the form data to the Banking Application. We have a text
field to store the Account Number and a hidden field to store some hard-coded PIN. Once
the user clicks on the Claim My Gift Card button, the request will be submitted to the
Banking Application, where it will update the Account Number and PIN.
@{

ViewData["Title"] = "Claim Gift Card Page";

<div class="container">

<div class="row justify-content-center">

<div class="col-md-6">

<div class="text-center">

<h3>Enter your Account Number to Claim Your Gift</h3>

<form action="https://localhost:7199/Home/ChangePin" method="post" class="form-


group">

<div class="form-group mt-3">


508

<input type="text" name="AccountNumber" id="AccountNumber" class="form-control"


placeholder="Account Number" />

</div>

<div class="form-group mt-3">

<input type="hidden" name="Pin" id="Pin" value="1111" />

<button type="submit" class="btn btn-primary">Claim Your Gift</button>

</div>

</form>

</div>

</div>

</div>

</div>
Now, run the application, enter your account number, and click the Claim Your Gift button,
as shown in the image below.

Once you click on the Claim Your Gift button, your account number pin is updated in the
Banking Application as shown in the below image:
509

This is nothing but a Cross-Site Request Forgery (CSRF or XSRF) attack on a website.
How can we prevent Cross-Site Request Forgery (CSRF or XSRF) Attack
in ASP.NET Core MVC?
To prevent Cross-Site Request Forgery (CSRF or XSRF) in ASP.NET Core MVC Web
Applications, we need to use AntiForgery Tokens. ASP.NET Core MVC uses AntiForgery
Tokens, also known as request verification tokens, to prevent CSRF attacks. These tokens
are unique to each user session and confirm that the user submitting a request is the one
who originally requested the page.
How Does It Work in ASP.NET Core?
 When a form is rendered, ASP.NET Core MVC injects a hidden form field with
the anti-forgery token.
 The user’s browser submits this token along with the form data.
 Upon receiving the request, the server validates the token to ensure that the
request is legitimate.
How to Use AntiForgery Tokens in ASP.NET Core MVC?
To generate the anti-forgery token, we need to use the @Html.AntiForgeryToken() helper
method within the <form> tag. Then, we need to decorate the action method that handles
the posted form data with the [ValidateAntiForgeryToken] attribute. The
[ValidateAntiForgeryToken] attribute will ensure that the action method processes the
request only if it comes with a valid anti-forgery token.
From ASP.NET Core 2.0 onwards, the framework automatically generates anti-forgery
tokens for all forms by default, so we don’t need to use
the @Html.AntiForgeryToken() helper method. The framework validates the token
automatically when the action method is decorated with
the [ValidateAntiForgeryToken] attribute. If the token is missing or invalid, it rejects the
request. So, let us proceed and implement this in our Banking Application:
Modify the ChangePin.cshtml view of the Home Controller of our banking application as
follows. Here, we are adding the @Html.AntiForgeryToken() helper method within the
form body. The @Html.AntiForgeryToken() method generates a hidden input field
containing the anti-forgery token.
@{
510

ViewData["Title"] = "Pin Change Page";

<div>

<form asp-controller="Home" asp-action="ChangePin" method="post" class="mt-3">

@Html.AntiForgeryToken()

<div style="margin-top:7px" class="form-group row">

<label for="AccountNumber" class="col-sm-2 col-form-label">Account Number</label>

<div class="col-sm-10">

<input type="text" name="AccountNumber" id="AccountNumber" class="form-control" />

</div>

</div>

<div style="margin-top:7px" class="form-group row">

<label for="Pin" class="col-sm-2 col-form-label">Pin</label>

<div class="col-sm-10">

<input type="text" name="Pin" id="Pin" class="form-control" />

</div>

</div>

<div style="margin-top:10px" class="form-group row">

<div class="col-sm-10">

<button type="submit" class="btn btn-primary">PIN Change</button>

</div>

</div>

</form>

</div>
511

Next, modify the Home Controller of our Banking Application as follows. Here, we are
decorating the Post ChangePin action method with the ValidateAntiForgeryToken attribute.
The [ValidateAntiForgeryToken] attribute ensures the request includes a valid anti-forgery
or request verification token.
using Microsoft.AspNetCore.Mvc;

namespace BankingApplication.Controllers

public class HomeController : Controller

[HttpGet]

public IActionResult ChangePin()

return View();

[HttpPost]

[ValidateAntiForgeryToken]

public ActionResult ChangePin(string AccountNumber, string Pin)

// Process the data

TempData["Message"]= $"AccountNumber: {AccountNumber} Pin Changed to: {Pin}";

return RedirectToAction("PinChangeSuccess");

public ActionResult PinChangeSuccess()

return View();

}
512

}
With the above changes, run the Banking Application; you will see that the account number
pin updation functionality is working as expected. Now, run the Hacker Application and click
on the Claim My Gift Card button by entering the Account, and you should see the
following error page:

The anti-forgery token filed name is __RequestVerificationToken, and if you want, you
can also capture this field value in your application code. For example, modify the Home
Controller of Banking Application as follows:
using Microsoft.AspNetCore.Mvc;

namespace BankingApplication.Controllers

public class HomeController : Controller

[HttpGet]

public IActionResult ChangePin()

{
513

return View();

[HttpPost]

[ValidateAntiForgeryToken]

public ActionResult ChangePin(string AccountNumber, string Pin, string


__RequestVerificationToken)

// Process the data

TempData["Message"]= $"AccountNumber: {AccountNumber} Pin Changed to: {Pin}";

return RedirectToAction("PinChangeSuccess");

public ActionResult PinChangeSuccess()

return View();

}
How AntiForgeryToken Generated and Validated in ASP.NET Core MVC?
ASP.NET Core generates a unique token called the AntiForgeryToken when a form is
rendered. This token is created using the @Html.AntiForgeryToken() helper method in
Razor views. The generated token consists of two parts: a hidden field in the form and a
cookie. The hidden field ensures the token is sent along with the form data during a POST
request while the cookie is stored in the user’s browser. The token is encrypted and
securely signed to ensure that it cannot be tampered with by an attacker. So, when we send
the request to the server, the token is sent using the hidden form field as well as through the
cookie header.
In ASP.NET Core MVC, when a form is submitted, the framework automatically validates
the AntiForgeryToken if the [ValidateAntiForgeryToken] attribute is applied to an action
method. During validation, the framework compares the token from the hidden field (sent in
the POST request) with the token coming from the request header cookie. If the tokens do
514

not match or are missing, ASP.NET Core will throw an exception, preventing the action
method from executing, thereby stopping the potential CSRF attack.

Introduction to Entity Framework Core


This is the first article of the Entity Framework Core article series, and in this article, I will
give you an overview of Entity Framework Core. The Entity Framework Core, or EF Core,
is the latest version of Entity Framework and completely rewrites from the ground up. As
part of this article, we will discuss the following pointers.
1. What is Entity Framework Core?
2. What is ORM?
3. Why Do We Need to Use An ORM?
4. Why Entity Framework Core in .NET Applications?
5. EF Core Development Approaches
6. EF Core Code First Approach
7. EF Core Database First Approach
8. EF Core Database Providers
9. Why We Need to Use Entity Framework Core over EF 6.x?
10. Differences Between EF Core and EF6
What is Entity Framework Core?
Entity Framework (EF) Core is an ORM (Object-Relational Mapper) Framework for data
access in .NET Core. It was released along with .NET Core and is an Extensible,
Lightweight, Open Source, and Cross-Platform Version of Entity Framework data access
technology. It works on multiple operating systems like Windows, Mac, and Linux.
The Entity Framework is an Object/Relational Mapping (O/RM) framework that maps
objects to relational databases. EF Core is designed to work with .NET Core applications
but can also be used with standard .NET Framework applications based on Framework 4.5
or higher. The following diagram shows the supported types of applications that we can
develop using EF Core.
515

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:09 / 03:1710 Sec

What is ORM?
The term ORM stands for Object-Relational Mapper, and it automatically creates classes
based on database tables and vice versa is also true. It can also generate the necessary
SQL to create the database based on the classes.
As a developer, we mostly work with data-driven applications, and the ORM Framework
generates the necessary SQL (to perform the CRUD operation) that the underlying
database can understand. So, in simple words, we can say that the ORM Framework
eliminates the need for most of the data access code that, as a developer, we generally
write.
Why Do We Need to Use An ORM Framework?
Let us understand why we need to use the ORM Framework with an example. Suppose we
want to develop an application to manage the students of a college. To do this, we may
need to create classes such as Student, Department, Address, etc. Technically, we called
these classes Domain classes or business objects.
Without using an ORM Framework like Entity Framework (.NET Framework) or EF Core
(.NET Core), we have to write lots of data access code to perform the CRUD operations,
i.e., store and retrieve the Student, Department, and Address data from the underlying
database tables.
For example, to perform CRUD operations, i.e., read, insert, update, or delete from a
database table, we generally need to write custom code in our application to generate the
required SQL statements, which the underlying database can understand. Again, when we
want to read the data from the database into our application, we also have to write some
custom code to map the data to our model classes like Student, Department, Address, etc.
This is a very common task as a developer for us that we do almost in every application.
An ORM Framework like Entity Framework or EF Core can do all of the above for us and
saves a lot of time if we provide the required information to the ORM Framework. The ORM
Framework sits between our application code and the Database. It eliminates the need for
most custom data-access codes we usually write without an ORM.
516

Why Entity Framework Core in .NET Applications?


Entity Framework Core is an ORM Tool that increases the developer’s productivity by
reducing the redundant task of doing CRUD operations against a database in a .NET Core
Application.
1. Entity Framework Core (EF Core) can generate the necessary database
commands for doing the database CRUD Operation, i.e., it can generate
SELECT, INSERT, UPDATE, and DELETE commands for us.
2. While working with Entity Framework Core, we can perform different
operations on the domain objects (basically classes representing database
tables) using LINQ to Entities.
3. Entity Framework Core will generate and execute the SQL Command in the
database and then store the results in the instances of your domain objects
so you can do different types of operations on the data.
EF Core Development Approaches:
Entity Framework Core supports two development approaches. They are as follows:
1. Code-First Approach
2. Database-First Approach
EF Core Code First Approach:
In the EF Core Code First Approach, first, we need to create our application domain
classes, such as Student, Branch, Address, etc., and a special class (called DBContext
Class) that derives from the Entity Framework DbContext class. Then, based on the
application domain classes and DBContext class, the EF Core creates the database and
related tables. For a better understanding, please have a look at the following diagram.
517

In the Code-First Approach, the EF Core creates the database and tables using Code-First
Migration based on the default conventions and configuration. You can also change the
default conventions used to create the database and its related tables. This approach is
useful in Domain-Driven Design (DDD), i.e., creating the database tables based on the
Domain classes.
EF Core Database First Approach:
If you have an existing database and database tables are already there, you must use the
EF Core Database First Approach. In the database-first approach, the EF Core creates the
DBContext and Domain Classes based on the existing database schema using EF Core
Command.

EF Core Database Providers:


The EF Core supports relational and non-relational databases, which is possible due to the
database providers. The database providers are available as NuGet Packages. The
Database Provider sits between the EF Core and the Database it supports.
518

The EF Core database provider usually contains the functionality specific to the database it
supports.
1. Functionality common to all the databases is in the EF Core Component.
2. Functionality Specific to a Database, for example, Microsoft SQL Server-
Specific Functionality, is within the SQL Server Provider for EF Core.
Different Entity Framework Core Database providers are available for the different
databases. Some of them are as follows:
Azure SQL and SQL Server 2012 onwards: Microsoft.EntityFrameworkCore.SqlServer
SQLite 3.7 onwards: Microsoft.EntityFrameworkCore.Sqlite
EF Core in-memory database: Microsoft.EntityFrameworkCore.InMemory
PostgreSQL: Npgsql.EntityFrameworkCore.PostgreSQL
MySQL: MySql.EntityFrameworkCore
Oracle DB 11.2 onwards: Oracle.EntityFrameworkCore
For the complete list of EF Core Database Providers, please visit the following URL.
https://learn.microsoft.com/en-us/ef/core/providers/
Why We Need to Use Entity Framework Core over EF 6.x?
The EF 6.x is a stable and fully tested ORM technology in many .NET Framework
applications. EF Core also provides an experience similar to EF 6.x. However, it is built
upon a new set of components. The application targets .NET Core, e.g., ASP.NET Core
Applications. Entity Framework Core is the new and improved version of the Entity
Framework for .NET Core applications. EF Core continues to support the same features
and concepts as EF 6.
1. DbContext and DbSet
2. Data Model
3. Querying using Linq-to-Entities
4. Change Tracking
5. SaveChanges
6. Migrations
EF Core will include most of the features of EF 6 gradually. However, some features of EF 6
are not supported in EF Core, such as:
1. EDMX/ Graphical Visualization of Model
2. Entity Data Model Wizard (for DB-First approach)
3. ObjectContext API
4. Querying using Entity SQL.
5. Inheritance: Table per type (TPT)
6. Inheritance: Table per concrete class (TPC)
519

7. Entity Splitting
8. Spatial Data
9. Stored Procedure mapping with DbContext for CUD operation
10. Seed Data
EF Core includes the following new features which are not supported in EF 6.x:
1. Easy Relationship Configuration
2. Batch INSERT, UPDATE, and DELETE operations
3. In-memory provider for testing
4. Support for IoC (Inversion of Control)
5. Unique Constraints
6. Shadow Properties
7. Alternate Keys
8. Global Query Filter
9. Field Mapping
10. DbContext Pooling
11. Better Patterns for Handling Disconnected Entity Graphs
The Entity Framework Core is an updated and enhanced version of the Entity Framework
designed for .NET Core applications. While the EF Core is relatively new and not as fully
developed as the EF 6, it still supports its predecessor’s features and concepts. EF Core
offers new features that won’t be implemented in EF6. However, not all EF6 features are
currently implemented in EF Core. Please check the following link for more detailed
information.
https://learn.microsoft.com/en-us/ef/efcore-and-ef6/
Differences Between EF Core and EF6:
Entity Framework (EF) has been a popular ORM (Object-Relational Mapper) in the .NET
ecosystem. It has gone through two major iterations: EF6 and EF Core. Here are the
primary differences between the two:
Development Platform:
 EF6: EF6 is a Windows-only framework within .NET, not compatible with
other platforms.
 EF Core: EF Core is part of the .NET Core Ecosystem, which means it is
cross-platform and can be used in applications that run on Windows, Linux,
and macOS.
Performance:
 EF6: Although EF6 is feature-rich and robust, it has been criticized for its
performance in certain scenarios, particularly when performing batch
operations.
 EF Core: EF Core is a performance-focused framework, built from scratch.
It’s generally considered to be faster and more efficient than EF6.
Features:
1. EF6: EF6 is an ORM Framework with advanced features. Initially, some
features like Lazy Loading and Entity Graphs were not supported in EF, but
they were added later.
2. EF Core: EF Core started with a smaller feature set of features but then it
introduced many new features such as Global Query Filters and Shadow
Properties, etc.
520

Database Providers:
 EF6: Although EF6 supports multiple database providers, integrating third-
party providers can be a challenge.
 EF Core: EF Core has been designed to support various databases,
including NoSQL databases, with a more flexible provider model. You can
easily integrate third-party providers.
Configuration:
 EF6: In Entity Framework 6, configuration can be done using either a code-
based or XML-based (in .config files) approach.
 EF Core: EF Core uses code-based configuration instead of XML-based
configurations. The .NET Core philosophy is to move away from XML-based
configurations.
Community and Future:
 EF6: Although EF6 is still maintained and receives updates, the primary focus
of the EF team and community is on EF Core.
 EF Core: EF Core is the future of Entity Framework. It receives regular
updates, new features, and performance improvements.
If you’re working on an existing project that already using EF6 or one that is linked to
the .NET Framework, it’s still a good option to use EF 6. However, if you are developing
new projects, particularly projects targeting .NET Core or .NET 5/6+, EF Core is the best
choice. EF Core’s flexibility, performance improvements, and cross-platform capabilities
make it the perfect selection for modern application development.
Prerequisites to Learn Entity Framework Core:
To take full advantage of this Entity Framework Core Tutorials, you should have the basic
knowledge of C# and any database such as SQL Server, Oracle, or MySQL to gain more
knowledge of these tutorials. Having .NET Core, Visual Studio, and SQL Server installed on
your computer is good.

How to Install Entity Framework Core in .NET Core Application


In this article, I will discuss How to Install Entity Framework Core using Visual Studio in
.NET Core Application step by step with different approaches. Please read our previous
article, which discussed Introduction to Entity Framework Core. The Entity Framework
Core can be used with .NET Core applications and .NET 4.6-Based (.NET Framework)
Applications.
How to Install Entity Framework Core?
In this article, I will show you how to install Entity Framework Core in .NET Core
applications using Visual Studio 2022. Once we understand the Entity Framework Core
Basic Concepts, I will show you How to use Entity Framework Code First and Database
First Approach in ASP.NET Core MVC Application.
Note: The steps that are going to be followed to Install Entity Framework Core are going to
be the same irrespective of the type of .NET Core Application, such as Console, Class
Library, MVC, Web API, etc.
521

The Entity Framework Core is not a part of the .NET Core and standard .NET framework. It
is available as a NuGet package. We need to install the following two NuGet packages to
use EF Core in our application:
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV

1. EF Core DB Provider
2. EF Core Tools
Let’s install the above two NuGet Packages in the .NET Core Console application using
Visual Studio 2022. First, open Visual Studio 2022 and click “Create a new Project”, as
shown in the image below, to create a new .NET Console Application.

Then select the Console App, which is targeting the .NET Core, and then click on the Next
button, as shown in the below image. As I am using Windows OS, I have filtered the
language as C#, OS as Windows, and Application type as Console.
522

Once you click on the Next button, it will open the following Configure Your New Project
window. Here, you need to provide the Project Name (I am providing the project name
as EFCoreCodeFirstDemo), the location (I am creating in D:\EFCoreProjects) where you
want to create the project, and the solution name (I am keeping the same as the project
name) and then click on the Next button as shown in the below image.

Once you click on the Next button, it will open the Additional Information window. Here,
select the Target .NET Framework. I am selecting .NET 6, and I don’t want to use the top-
level statements, so I am checking the Do not use top-level statement checkbox and finally
clicking on the Create button, as shown in the image below.

Once you click the Create button, it will create the Console Application using .NET 6 with
the following structure.
523

As you can see, Entity Framework Core is not installed by default. So, let us proceed and
try to understand How to Install Entity Framework Core in .NET Core Application.
Installing Entity Framework Core in .NET Core Application:
Entity Framework Core is not part of the .NET Core Framework. So, we need to install
Entity Framework Core using NuGet packages to use Entity Framework. We need to install
the following two NuGet packages to use EF Core in our .NET Core Application:
1. EF Core DB Provider
2. EF Core Tools
Installing EF Core DB Provider:
As discussed in our previous article, Entity Framework Core allows us to access databases
via the provider model. Different Entity Framework Core Database providers are available
for the different databases. Some of them are as follows:
Azure SQL and SQL Server 2012 onwards: Microsoft.EntityFrameworkCore.SqlServer
SQLite 3.7 onwards: Microsoft.EntityFrameworkCore.Sqlite
EF Core in-memory database: Microsoft.EntityFrameworkCore.InMemory
PostgreSQL: Npgsql.EntityFrameworkCore.PostgreSQL
MySQL: MySql.EntityFrameworkCore
Oracle DB 11.2 onwards: Oracle.EntityFrameworkCore
For the complete list of EF Core Database Providers, please visit the following URL.
https://learn.microsoft.com/en-us/ef/core/providers/
These providers are available as NuGet packages. So, we need to install the NuGet
Package for the database provider we want to access. I will use Microsoft SQL Server as
the backend database in this course. So, we need to
install Microsoft.EntityFrameworkCore.SqlServer package from NuGet.
To Install Entity Framework Core using NuGet packages, Right click on the Project and then
click on the Manage NuGet Packages option from the context menu, as shown in the image
below. Alternatively, select Tools -> NuGet Package Manager -> Manage NuGet
Packages for Solution from the Visual Studio menus.
524

Once you select Manage NuGet Packages, it will open the NuGet Package Manager UI, as
shown in the image below. Select the Browse Tab, then search
for Microsoft.EntityFrameworkCore.SqlServer and then
select Microsoft.EntityFrameworkCore.SqlServer Package and select the Framework
Version that you want to Install. By default, the latest version will be selected, and currently,
the latest version of Microsoft.EntityFrameworkCore.SqlServer package is 7.0.9 (it might
be changed while you read this article). finally, click on the Install button as shown in the
below image.

Once you click on the Install Button, the Preview Changes window will pop up, showing the
list of packages it will install in your application. Review the changes and click the OK
button, as shown in the image below.
525

Once you click on the OK button, it will open the License Acceptance pop-up. So, finally,
accept the license terms associated with the packages that will be installed by clicking on
the “I Accept” button, as shown in the image below.
526

Once you click on the I Accept button, it will


install Microsoft.EntityFrameworkCore.SqlServer Package within the Packages folder,
which you can find inside the Dependencies folder of your project, as shown in the below
image. That means Microsoft.EntityFrameworkCore.SqlServer Package is installed.

Alternatively, you can install the provider’s NuGet Package using the Package Manager
Console. Go to Tools -> NuGet Package Manager -> Package Manager Console and
527

then execute the following command to install the Entity Framework Core SQL Server
Provider package:
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
Note: If you want to use a different database with your application, install that provider-
specific NuGet Package instead of Microsoft.EntityFrameworkCore.SqlServer database
provider package.
For example, if you want to use MySQL as your database, install
MySql.EntityFrameworkCore database provider package. Along the same lines, if you want
to use PostgreSQL as your database, use NDevart.Data.PostgreSql.EFCore database
provider package. For the complete list of EF Core Database Providers, please visit the
following URL.
https://learn.microsoft.com/en-us/ef/core/providers/
What is the Role of EF Core DB Provider?
When you work with Entity Framework Core (EF Core), the database provider acts as a
bridge between the EF Core API and a particular database management system (DBMS).
The primary role of the DB provider is to convert the operations you define in EF Core into
commands that the underlying DBMS can understand because each DBMS has its own set
of features, rules, syntax, and configurations. The following tasks are going to be performed
by EF Core DB Provider.
 Query Translation: When you execute a LINQ query using EF Core, it
doesn’t run as a LINQ query on objects in memory. Instead, EF Core converts
the query to SQL (or another suitable query language for the selected
DBMS). The DB provider is responsible for translating the query to ensure
that the generated SQL is going to be understood and executed by the
corresponding DBMS.
 Change Tracking: EF Core monitors entity changes to entities retrieved from
the database and then generates appropriate SQL commands such as
INSERT, UPDATE, and DELETE for persistence based on the Entity State. If
the entity stated is Added, it will generate an INSERT SQL Statement, if the
entity state is Modified, it will generate an UPDATE SQL Statement and if the
entity state is Deleted, then it will generate the DELETE SQL Statement.
 Connection Management: The DB Provider also manages tasks involving
database connections, including opening and closing connections as well as
managing transactions.
 Migrations: With the Migrations feature of EF Core, we can easily manage
and version our database schema. The database provider will translate the
migration commands into SQL to modify the database schema ensuring that
the generated SQL is suitable for the target database. If you are using the
SQL Server Database Provider, then it will generate SQL Statements which
are going to be understood by the SQL Server Database. Similarly, If you are
using the MySQL Database Provider, then it will generate SQL Statements
that are going to be understood by the MySQL Database.
 Database-Specific Features: Some databases have some features that
aren’t common across all DBMSs. For example, the SQL Statement we write
to get the TOP recorded in the SQL Server database is not the same as in the
MySQL Database. So, to guarantee smooth integration with the wider EF
Core API, the DB provider can give access to these exceptional features to
EF Core.
528

 Scaffolding: EF Core can generate a code-first model by reverse-


engineering a database. The DB provider reads the schema and data of the
existing database to inform EF Core of its structure. So, basically, we need to
use Scaffolding when we are going to work with the Entity Framework Core
Database First Approach.
 Type Mapping: The EF Core DB provider manages the mapping
between .NET types and the corresponding DBMS data types. For example, it
will handle how a .NET DateTime is stored in a specific database’s date/time
column type.
Installing Entity Framework Core Tools
Along with the Entity Framework Database provider package, we must install Entity
Framework Core tools that provide a command line and .NET CLI tools that assist
developers in tasks related to the Entity Framework. These tools simplify tasks like creating
migrations, updating the database with migrations, querying the database, and more. If this
is unclear now, don’t worry; we will understand these things practically in our upcoming
articles.
In the same way, we need to install Microsoft.EntityFrameworkCore.Tools package. Go
to NuGet Package Manager UI and search
for Microsoft.EntityFrameworkCore.Tools package, then
select Microsoft.EntityFrameworkCore.Tools package. Select the latest stable version
and click the Install button in the image below. The latest stable package at the time of
creating this content is 7.0.9.

Once you click on the Install Button, the Preview Changes window will pop up, showing the
list of packages that will be installed. Simply click on the OK button as shown in the below
image.
529

Once you click on the OK button, it will open the License Acceptance pop-up, and you need
to click on the “I Accept” button, as shown in the image below.
530

Once you click on the I Accept button, it will


install Microsoft.EntityFrameworkCore.Tools Package within the Packages folder, which
you can find inside the Dependencies folder of your project. After successfully installing the
packages, they can be verified from Solution Explorer under the Dependencies =>
Packages, as shown in the image below.
531

Alternatively, you can install the EF Core Tool Package using the Package Manager
Console. Go to Tools -> NuGet Package Manager -> Package Manager Console and
then execute the following command to install the Entity Framework Core Tool package:
PM> Install-Package Microsoft.EntityFrameworkCore.Tools
What is the Role of EF Core Tools?
Entity Framework Core (EF Core) Tools provide a set of command-line tools and .NET CLI
tools that assist developers in tasks related to the Entity Framework. These tools simplify
tasks like creating migrations, updating the database with migrations, querying the
database, and more. The primary roles of EF Core Tools are as follows:
Database Migrations:
 Creating Migrations: Generate code files that represent the model changes,
which can be applied to the database schema.
 Updating the Database: Apply migrations to update the database schema to
the latest version of the migration.
 Removing Migrations: Remove the latest migration, allowing you to make
additional changes before regenerating the migration.
 Generating SQL Scripts: Produce SQL scripts from migrations and this is
useful when direct database updates are not feasible (e.g., in strict production
environments).
Database Scaffolding: EF Core Tools can create code-first model files from an existing
database. This is useful when we have an existing database and want to represent it as a
code-first model in our application.
View DbContext Model: EF Core Tools enable creating a visual representation (DGML file)
of DbContext to understand entity relationships and structure.
EF Core Tools are available as PowerShell commands (in the Visual Studio Package
Manager Console) and as .NET CLI commands. Some commonly used commands are as
follows:
PowerShell Commands (Package Manager Console in Visual Studio):
1. Add-Migration
2. Update-Database
3. Remove-Migration
4. Scaffold-DbContext
.NET CLI Commands:
1. dotnet ef migrations add
2. dotnet ef database update
532

3. dotnet ef migrations remove


4. dotnet ef dbcontext scaffold
To use EF Core Tools, developers typically
install Microsoft.EntityFrameworkCore.Tools NuGet package for PowerShell commands
and Microsoft.EntityFrameworkCore.Design package for .NET CLI commands.

DbContext Class in Entity Framework Core


In this article, I will discuss the DbContext Class in Entity Framework Core. Please read
our previous article, discussing How to Install Entity Framework Core in .NET
Applications. The DbContext class is one of the important classes in the Entity Framework
core. At the end of this article, you will understand the significance of the DbContext class in
the Entity Framework Core.
Note: We will work with the example we created in our previous article and discuss How to
Install Entity Framework Core in our .NET Application.
What is the DbContext Class in EF Core?
According to MSDN, an instance of DbContext represents a session with the database and
can be used to query and save instances of your entities. DbContext is a combination of the
Unit of Work and Repository patterns.
The DbContext class is an integral part of the Entity Framework. The DBContext Class in
Entity Framework Core is used by our application to interact with the underlying database.
This class manages the database connection and performs CRUD Operations with the
underlying database. The DbContext in Entity Framework Core Performs the following
tasks:

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:07 / 03:1710 Sec
 Object Set Representation: DbContext represents a session with the
database that allows querying and saving of entity classes via
DbSet<TEntity> properties for each entity type in the model.
 Change Tracking: When retrieving data from the database using the
DbContext, the changes made to that data are tracked by the DbContext. If
any modifications, the entity’s properties are updated, DbContext keeps track
of those changes. This enables EF Core to generate appropriate SQL
statements to reflect the database changes accurately.
 Database Operations: Using methods on DbContext, such as
SaveChanges(), we can insert, update, or delete records in the database.
That means the changes we make to our entities will be updated in the
database when we call the SaveChanges() method of the DbContext object.
 Configuration: The DbContext allows for the configuration of the database
connection. It also tells how models are mapped to database schemas,
caching policies, etc. We need to do this by overriding the OnConfiguring or
OnModelCreating methods in our DbContext class (A class that is inherited
from the DbContext class).
533

 Querying: DbContext allows us to use LINQ to construct database queries


that are automatically translated into SQL queries based on the database
provided and will be executed on the corresponding database.
 Lazy Loading: The DbContext class is also responsible for enabling lazy
loading, which automatically loads the related data from the database when
the navigation property is being accessed.
 Caching: Remember to use the DbContext to perform first-level caching by
default. This means that if you request the same data multiple times while the
DbContext instance is active, it will return the cached version of the data
instead of querying the database again. This can greatly improve
performance if you need to access the same data repeatedly.
 Unit of Work: DbContext acts as a unit of work pattern, batching all database
DML operations into a single transaction to ensure data consistency. That
means that by using DbContext, we can also implement transactions.
 Connection Management: DbContext manages database connections, i.e.,
it opens and closes the database connection as and when needed and
handles transactions for batch operations. Batch operation means executing
multiple SQL Statements.
 Migrations: The DbContext plays an important role in creating and managing
database migrations, which are a way to manage database schema changes.
 Relationship Management: DbContext manages relationships (one-to-one,
one-to-many, and many-to-many) between entities and supports navigation
properties for foreign key relationships in the database.
 Database Provider Agnosticism: When using DbContext, your code will be
compatible with multiple databases (SQL Server, PostgreSQL, SQLite)
regardless of the specific database provider. So, our code will be the same
irrespective of the database provider and the backend database.
Example to Understand DbContext Class in Entity Framework Core:
Let us understand the need and use of the DbContext Class with an example. At the root
directory of your project, create a folder and name it as Entities. Then, inside this folder,
create two class files named Student.cs and Standard.cs.
So, create a class file within the Entities folder named Student.cs, and then copy and paste
the following code. As you can see, here, we are creating the Student class with a few
scalar properties and one Reference Navigation property called Standard, which makes the
relationship between Student and Standard entities one-to-one. In the next step, we are
going to create the Standard entity.
using Microsoft.EntityFrameworkCore.Metadata.Internal;

using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Student

{
534

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public DateTime? DateOfBirth { get; set; }

[Column(TypeName = "decimal(18,4)")]

public decimal Height { get; set; }

[Column(TypeName = "decimal(18,4)")]

public float Weight { get; set; }

public virtual Standard? Standard { get; set; }

}
Next, create another class file within the Entities folder named Standard.cs, and then copy
and paste the following code. As you can see, here, we are creating the Standard class with
few scalar properties and one collection Navigation property called Students, which makes
the relationship between Standard and Student entities one-to-many.
namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int StandardId { get; set; }

public string? StandardName { get; set; }

public string? Description { get; set; }

public ICollection<Student>? Students { get; set; }

}
535

Now, we are done with the initial domain classes for our application. Later, as we progress,
we will add more domain classes to this example.
How to Create a DbContext Class in Entity Framework Core?
The main class that coordinates Entity Framework Core functionality for a given data model
is the database context class, which allows to query and save data. The Entity Framework
Core Code-First Approach requires a user-defined context class, which should be derived
from the DbContext class.
To use the DbContext class in our .NET Application, we must create a class derived from
the DbContext class. The DbContext class is present
in Microsoft.EntityFrameworkCore namespace. The Entity Framework
Core DbContext class includes a property, i.e., DbSet<TEntity>, for each entity in your
application.
So, create a class file within the Entities folder named EFCoreDbContext.cs and then copy
and paste the following code into it. You can give any name for your context. But the class
should and must be derived from the DbContext class and exposes DbSet properties for the
types you want to be part of the model. For example, Student and Standard domain classes
in this case. The DbSet is a collection of entity classes (i.e., entity set). Here, we have given
the property name in the plural form of the entity name, like Students and Standards, as
Microsoft recommends. The DbContext class belongs to Microsoft.EntityFrameworkCore
namespace.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//use this to configure the contex

}
536

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//use this to configure the model

//Adding Domain Classes as DbSet Properties

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
As you can see in the above code, the EFCoreDbContext class is derived from the
DbContext class and contains the DbSet<TEntity> properties of Student and Standard type.
The parameterless constructor of our context class is called the base DbContext Class
constructor. It also overrides the OnConfiguring and OnModelCreating methods. We must
create an instance of the EFCoreDbContext class to connect to the database and save or
retrieve Student or Standard data.
The OnConfiguring() method allows us to select and configure the data source to be used
with a context using DbContextOptionsBuilder. In our upcoming article, we will discuss more
about this method.
The OnModelCreating() method allows us to configure the model using ModelBuilder Fluent
API. Again, in our upcoming article, we will discuss more about this method.
Note: we have included two model classes as DbSet properties, and the entity framework
core will create two database tables for the above two model classes with the required
relationships.
DbContext Methods in Entity Framework Core:
1. Add: Adds a new entity to DbContext with an Added state and starts tracking
it. This new entity data will be inserted into the database when
SaveChanges() is called.
2. AddAsync: Asynchronous method for adding a new entity to DbContext with
Added state and starts tracking it. This new entity data will be inserted into the
database when SaveChangesAsync() is called.
3. AddRange: Adds a collection of new entities to DbContext with Added state
and starts tracking it. This new entity data will be inserted into the database
when SaveChanges() is called.
4. AddRangeAsync: Asynchronous method for adding a collection of new
entities, which will be saved on SaveChangesAsync().
537

5. Attach: Attaches a new or existing entity to DbContext with an Unchanged


state and starts tracking it. In this case, nothing will happen when we call the
SaveChanges method, as the entity state is not modified.
6. AttachRange: Attaches a collection of new or existing entities to DbContext
with an Unchanged state and starts tracking it. In this case, nothing will
happen when we call the SaveChanges method, as the entity state is not
modified.
7. Entry: Gets an EntityEntry for the given entity. The entry provides access to
change tracking information and operations for the entity. So, using this
method, we can manually set the Entity State, which DbContext will also
track.
8. Find: Find an entity with the given primary key values.
9. FindAsync: Asynchronous method for finding an entity with the given primary
key values.
10. Remove: It sets the Deleted state to the specified entity, which will delete the
data when SaveChanges() is called.
11. RemoveRange: Sets Deleted state to a collection of entities that will delete
the data in a single DB round trip when SaveChanges() is called.
12. SaveChanges: Execute INSERT, UPDATE, or DELETE commands to the
database for the entities with Added, Modified, or Deleted state.
13. SaveChangesAsync: Asynchronous method of SaveChanges()
14. Set: Creates a DbSet<TEntity> that can be used to query and save instances
of TEntity.
15. Update: Attaches disconnected entity with Modified state and starts tracking
it. The data will be saved when SaveChagnes() is called.
16. UpdateRange: Attaches a collection of disconnected entities with a Modified
state and starts tracking it. The data will be saved when SaveChagnes() is
called.
17. OnConfiguring: Override this method to configure the database (and other
options) for this context. This method is called for each instance of the context
that is created.
18. OnModelCreating: Override this method to configure further the model
discovered by convention from the entity types exposed in DbSet<TEntity>
properties on your derived context.
DbContext Properties in Entity Framework Core:
1. ChangeTracker: Provides access to information and operations for entity
instances this context is tracking.
2. Database: Provides access to database-related information and operations
for this context.
3. Model: Returns the metadata about the shape of entities, the relationships
between them, and how they map to the database.
538

Database Connection String in Entity Framework Core


In this article, I will discuss Database Connection String in Entity Framework Core and
How to Generate a Database using the EF Core Code First Approach. Please read our
previous article discussing the DbContext Class in Entity Framework Core. We will work
with the example we created in our previous article and discuss How to Create EF Core
DbContext Class in our .NET Application.
Database Connection String in Entity Framework Core
Now, we will see the options available in .NET Core to Provide a Database Connection
String. The DBContext in EF Core Connects to the database using the Database Providers.
The database Providers require a connection string to connect to the database.
We can provide the database connection string to the EF Core application in several ways.
We look at some of them in detail. The connection strings were stored in the web.config file
in the older version of .NET Framework Applications. The newer .NET Core applications
can read the database connection string from various sources like appsettings.json, user
secrets, environment variables, and command line arguments; we can even hardcode the
connection string. You can store the connection string anywhere you wish to.
Providing Connection String in OnConfiguring Method of DbContext
Class:
To provide a Connection String in the OnConfiguring Method, please modify the DbContext
Class as follows:

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:16 / 03:1710 Sec

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)


539

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//use this to configure the model

//Adding Domain Classes as DbSet Properties

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
As you can see, the context class includes two DbSet<TEntity> properties for Student and
Standard, which will be mapped to the Students and Standards tables in the underlying
SQL Server database. In the OnConfiguring() method, an instance of
DbContextOptionsBuilder is used to specify which database to use. We have installed MS
SQL Server provider, which has added the extension method UseSqlServer on
DbContextOptionsBuilder.
The connection string “Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;
Database=EFCoreDB1; Trusted_Connection=True; TrustServerCertificate=True;” in
the UseSqlServer method provides database information:
1. Server= specifies the DB Server to use,
2. Database= specifies the name of the database to create
3. Trusted_Connection=True specifies the Windows authentication mode.
4. TrustServerCertificate=True specifies the transport layer will use SSL to
encrypt the channel and bypass walking the certificate chain to validate trust.
540

EF Core will use this connection string to create a database when we run the migration.
After Creating the Context Class and Entity Classes and specifying the Database
Connection String, it’s time to add the migration to create a database.
Adding Entity Framework Core Migration:
Entity Framework Core Includes different migration commands to create or update the
database based on the model. At this point, there is no EFCoreDB1 database in SQL
Server. So, we need to create the database from the model (Entities and Context) by
adding a migration.
We can execute the migration command using the NuGet Package Manager Console and
the dotnet CLI (command line interface) command.
In Visual Studio, open the NuGet Package Manager Console. To launch Package Manager
Console, select Tools => NuGet Package Manager => Package Manager Console from
the menu below.

This will open the Package Manager Console. Now, type the add-migration
CreateEFCoreDB1 command, select the project where your Context class is, and press the
enter button, as shown in the image below.
541

Once the above code is executed successfully, it will create a new folder
named Migrations in the project and create the ModelSnapshot files, as shown below. In
our upcoming article, we will discuss Migration files in detail.

If you use .NET Core CLI, use the following command.


dotnet ef migrations add CreateEFCoreDB1
After creating a migration, don’t think that the database is created. We still need to create
the database using the update-database –verbose command in the Package Manager
Console as below.

If you use dotnet CLI, enter the following command.


dotnet ef database update
Once the command is executed successfully, it will create the database with the name and
location specified in the connection string in the UseSqlServer() method. It creates a table
for each DbSet property (Students and Standards), as shown in the below image.

Note: This was the first migration to create a database. The most important point is that
whenever we add or update domain classes or configurations, we need to sync the
database with the model using add-migration and update-database commands.
542

Reading or Writing Data using DbContext Class:


Now, we can use the context class to save and retrieve data from the database using Entity
Framework Core. For a better understanding, please modify the Main method of the
Program class as follows. The following example code is self-explained, so please go
through the comment lines for a better understanding.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

using var context = new EFCoreDbContext();

try

//Create a New Student Object

var student = new Student()

FirstName = "Pranaya",

LastName = "Rout",

Height = 5.10M,

Weight = 50

};

//Add the Student into context object using DbSet Property and Add method

context.Students.Add(student);

//Call the SaveChanges method to make the changes Permanent into the Database
543

context.SaveChanges();

Console.WriteLine("Student Saved Successfully...");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Once you run the above code, you should get the following output.

Now, if you go and verify the database, you should see the newly added student inside the
Students database table, as shown in the image below.

Note: Hardcoding the application code’s connection string is a bad programming practice.
In real-time applications, we need to store the connection string in the appsettings.json file,
and from the configuration file, we need to read the connection string. But by default, the
appsettings.json file is unavailable in the Console Application. So, let us proceed and
understand the steps required to use the appsettings.json file in the Console Application,
How to use AppSettings.json files in .NET Core Console Application?
For the .NET Framework Console Application, we always use the app.config file to store our
configuration values for the application, such as Connection Strings, Application Level
Global Variables, etc. In .NET Core, instead of App.Config file: we need to use the
appsettings.json file. But, by default, this appsettings.json file is available in ASP.NET Core
Framework Applications. But, by default, not available for the Console Application. Let us
544

proceed and see how we can create and use the appsettings.json file in the .NET Core
Console Application.
To use the appsettings.json file in the Console Application, we need to install the following
package from NuGet either by using NuGet Package Manager UI or Package Manager
Console.
Microsoft.Extensions.Configuration.Json
So, open NuGet Package Manager UI by selecting Tools => NuGet Package Manager =>
Manage NuGet Packages for Solution from the Visual Studio Menu, which will open the
following window. Select the Search tab from this window, then search for
the Microsoft.Extensions.Configuration.Json package, select the package, and click the
Install button, as shown in the image below.

Once you click the Install button, it will open the Preview Changes window to tell what
packages (including the dependency packages) will be installed. Review the changes and
click the OK button in the image below.
545

Once you click on the OK button, it will install the package, and you can verify the package
inside the Dependencies => Packages folder of your project, as shown in the image below.

Adding appsettings.json File:


Once we install the package, the next step is to add a JSON file with the name
appsettings.json to your project. While the name does not need to always be appsettings,
this is a naming convention we generally follow in .NET Core Applications. So, right-click on
your project and select Add => New Item from the context menu to open the following Add
New Item window. Here, search for JSON and then select JavaScript JSON Configuration
File, provide the file name as appsettings.json, and click on the add button, which will add
appsettings.json to the root directory of your project.
546

Once you add the appsettings.json file, please open it and copy and paste the following
code. Here, we are adding the database connection string.
{

"ConnectionStrings": {

"SQLServerConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;"

}
How to Fetch the Connection String from the appsettings.json file?
We must follow the steps to get the Connection String from the appsettings.json file.
Step 1: Import the following namespace.
using Microsoft.Extensions.Configuration;
Step 2: Load the Configuration File.
var configBuilder = new
ConfigurationBuilder().AddJsonFile(“appsettings.json”).Build();
Step 3: Get the Section to Read from the Configuration File
var configSection = configBuilder.GetSection(“ConnectionStrings”);
Step 4: Get the Configuration Values based on the Config key.
var connectionString = configSection[“SQLServerConnection”] ?? null;
So, modify the EFCoreDbContext class to read the connection string from the
appsettings.json file. The following code is self-explained, so please go through the
comment lines for a better understanding.
547

using Microsoft.EntityFrameworkCore;

//Step1: Import the following Namespace

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.Logging;

using Microsoft.Extensions.Options;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Get the Connection String from appsettings.json file

//Step2: Load the Configuration File.

var configBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();

// Step3: Get the Section to Read from the Configuration File

var configSection = configBuilder.GetSection("ConnectionStrings");

// Step4: Get the Configuration Values based on the Config key.

var connectionString = configSection["SQLServerConnection"] ?? null;

//Configuring the Connection String


548

optionsBuilder.UseSqlServer(connectionString);

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//use this to configure the model

//Adding Domain Classes as DbSet Properties

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
Next, modify the Main method of the Program class as shown below.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

using var context = new EFCoreDbContext();

try

//Create a New Student Object


549

var student = new Student()

FirstName = "Pranaya",

LastName = "Rout",

Height = 5.10M,

Weight = 50

};

//Add the Student into context object using DbSet Property and Add method

context.Students.Add(student);

//Call the SaveChanges method to make the changes Permanent into the Database

context.SaveChanges();

Console.WriteLine("Student Saved Successfully...");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
With the above changes in place, now run the application, and you should get the following
Runtime Exception.

Why are we getting the above Exception?


550

The above exception clearly says that the appsettings.json file was not found inside the
project bin=>debug=>.net6.0 folder. That means we need to ensure that once we build the
project, the appsettings.json should be stored inside the above location. To do so, Right-
click the appsettings.json file to show its Properties window. Then, set its “Copy to Output
Directory” property to “Copy always.” This ensures the appsettings.json file is copied to the
Debug or Release folder every time the project is built.

So, with the above changes in place, build the project and run the application, and you
should get the output as expected, as shown in the below image.

CRUD Operations in Entity Framework Core (EF Core)


In this article, I will discuss CRUD Operations in Entity Framework Core (EF
Core). Please read our previous article discussing Database Connection String in Entity
Framework Core. We will work with the same example we have worked on so far.
CRUD Operations in Entity Framework Core (EF Core):
CRUD Operation means we need to perform Create, Retrieve, Update, and Delete
Operations. To perform the Insert, Update, and Delete operations in Entity Framework
Core, we have two persistence scenarios, i.e., connected and disconnected.
The same DbContext instance retrieves and saves the entities in the Connected scenario.
On the other hand, the DbContext instance differs in the disconnected scenario (One
DbContext instance retrieves the data, and another DbContext instance saves the data into
the database). In this article, we will learn about performing database CRUD operations in
the connected scenario. In our upcoming articles, we will discuss how to perform CRUD
Operations in a disconnected scenario.
An entity that contains data in its scalar property will be either INSERTED, UPDATED, or
DELETED, based on the State of the Entity. The Entity Framework Core API builds and
executes the INSERT, UPDATE, and DELETE SQL Statements for the entities whose
Entity State is Added, Modified, and Deleted, respectively, when the SaveChanges()
551

method is called on the DbContext instance. The following diagram shows the connected
scenario’s CUD (Create, Update, Delete) operations.

NextStayEnchanted by the Beautiful City near Cambodia Border - Nếm


TV00:38 / 02:4810 Sec

Note: In the connected scenario, the same DbContext instance keeps track of all the
entities, and when an entity is created, modified, or deleted, it automatically sets the
appropriate State for the entity.
Enabling EF Core Logging:
We often need to see or log the generated SQL Script and Change Tracking information for
debugging purposes. Entity Framework Core logging automatically integrates with the
logging mechanisms of .NET Core.
We can use the simple Entity Framework Core (EF Core) logging mechanism to log the
generated SQL while developing and debugging applications. This form of simple logging
requires minimal configuration and no additional NuGet packages.
EF Core Logs can be accessed using the LogTo method, and we need to configure this
method within the OnConfiguring method of the DbContext class. So, using
the DbContextOptionsBuilder instance, we need to call the LogTo method and tell where
to show the generated logs (Console.Write in our example) and what kind of logs
(Information, Trace, Debug, Error, Warning, etc.) you want to display. So, please modify the
EFCoreDbContext class as shown below to display the EF Core Log Information on the
Console window.
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext


552

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//use this to configure the model

//Adding Domain Classes as DbSet Properties

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
553

}
Inside the OnConfiguring method, we have added the following statement. The following
statement will log the generated SQL Script on the Console window.
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Create Operation using Entity Framework Core:
Create Operation means we need to add a new object. Adding a new object using Entity
Framework Core is very simple. First, you need to create an instance of the Entity you want
to add to the database. Once you created the instance of the entity, then you need to call
the Add Method of the DbSet or DbContext class and pass the entity. Finally, call
the SaveChanges method on the DbContext object, which will insert the new record into
the database by generating the INSERT SQL Statement.
Note: The DbSet.Add and DbContext.Add methods add a new entity to a context
(instance of DbContext), which will insert a new record in the database when you call the
SaveChanges() method.
For a better understanding, modify the Main method of the Program class as follows. In the
below example, context.Students.Add(newStudent) adds the newly created student entity
to the context object with Added Entity State. EF Core introduced the
new DbContext.Add method, which does the same thing as the DbSet.Add method. Most
often, we are going to use the DbContext.Add by omitting the type parameter because the
compiler will infer the type from the argument passed into the method. I have shown you
three ways to add the student entity to the context object here.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

//Create a new student which you want to add to the database

var newStudent = new Student()

FirstName = "Pranaya",
554

LastName = "Rout",

DateOfBirth = new DateTime(1988, 02, 29),

Height = 5.10m,

Weight = 72

};

//Create DBContext object

using var context = new EFCoreDbContext();

//Add Student Entity into Context Object

//Method1: Add Student Entity Using DbSet.Add Method

context.Students.Add(newStudent);

//Method2: Add Student Entity Using DbContext.Add Method with Type Parameter

//context.Add<Student>(newStudent);

//Method2: Add Student Entity Using DbContext.Add Method without Type Parameter

//context.Add(newStudent);

//Now the Entity State will be in Added State

Console.WriteLine($"Before SaveChanges Entity State:


{context.Entry(newStudent).State}");

//Call SaveChanges method to save student entity into database

context.SaveChanges();

//Now the Entity State will change from Added State to Unchanged State

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(newStudent).State}");

Console.WriteLine("Student Saved Successfully...");

catch (Exception ex)


555

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
With the above changes, run the application and get the following output.

If you verify the SQL Server database, you will see that the above entity is inserted into the
Students database table, as shown in the image below.

Update Operation in Entity Framework Core:


As discussed in the connected environment, the Entity Framework keeps track of all the
entities retrieved using the context object. Therefore, when we modify any entity data, the
Entity Framework will automatically mark the Entity State as Modified. When the
SaveChanges method is called, it updates the updated data into the underlying database by
generating the UPDATE SQL Statement.
The following code changes the student’s first and last names, whose ID is 1. The following
example code is self-explained, so please go through the comment lines.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

{
556

public class Program

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();

//Fetch the Student from database whose Id = 1

var student = context.Students.Find(1);

if(student != null)

//At this point Entity State will be Unchanged

Console.WriteLine($"Before Updating Entity State: {context.Entry(student).State}");

//Update the first name and last name

student.FirstName = "Prateek";

student.LastName = "Sahu";

//At this point Entity State will be Modified

Console.WriteLine($"After Updating Entity State: {context.Entry(student).State}");

//Call SaveChanges method to update student data into database

context.SaveChanges();

//Now the Entity State will change from Modified State to Unchanged State

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

Console.WriteLine("Student Updated Successfully...");


557

else

Console.WriteLine("Invalid Student ID : 1");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
In the above example, first, we retrieve the student from the database using the DbSet
Find method, which generates a SELECT SQL query. When we modify the FirstName and
LastName, the context object sets its Entity State to Modified. When we call the
SaveChanges() method, it builds and executes the Update SQL statement in the database.
Run the above code, and you will get the following output.

Note: The context object in Entity Framework Core keeps track of the columns of an entity
that are modified, and based on the modified columns, it will generate the UPDATE SQL
statement. Here, it will use the primary key column in the where condition. If one column
558

value is not updated, it will not include that column while updating the database. So you can
see the UPDATE statement. It only includes the First Name and Last Name columns.
Delete Operation in Entity Framework Core:
We need to use the Remove method of the DbSet or DbContext object to delete an entity
using Entity Framework Core. The DbSet or DbContext Remove method works for existing
and newly added entities tracked by the context object.
Calling the Remove method on an existing entity being tracked by the context object will not
immediately delete the entity from the database. Rather, it will mark the entity state as
Deleted. When we call the SaveChanged method, the Entity will be deleted from the
database by generating and executing the DELETE SQL Statement. Once the
SaveChanges method is executed successfully, it will mark the entity state as Detached,
which means the context object will not track the entity.
In the following example, we remove a student from the database whose StudentId is 1.
The following example code is self-explained, so please go through the comment lines.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();

//Find the Student to be deleted by Id

var student = context.Students.Find(1);

if (student != null)

//At this point the Entity State will be Unchanged

Console.WriteLine($"Entity State Before Removing: {context.Entry(student).State}");


559

//The following statement mark the Entity State as Deleted

context.Students.Remove(student);

//context.Remove<Student>(student);

//context.Remove(student);

//At this point, the Entity State will be in Deleted state

Console.WriteLine($"Entity State After Removing: {context.Entry(student).State}");

//SaveChanges method will delete the Entity from the database

context.SaveChanges();

//Once the SaveChanges Method executed successfully,

//the Entity State will be in Detached state

Console.WriteLine($"Entity State After Removing: {context.Entry(student).State}");

else

Console.WriteLine("Invalid Student ID: 1");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
560

}
In the above example, context.Students.Remove(student) marks the student entity state
as Deleted. When we call the SaveChanges method on the Context object, the Entity
Framework Core API generates and executes the DELETE SQL Statement in the database.
Then, it will mark the entity state as Detached, which means the context object is no longer
tracking this entity. Run the above application code, and you should get the following
output.

Entity States in Entity Framework Core (EF Core)


In this article, I will discuss Entity States in Entity Framework Core (EF Core). Please
read our previous article discussing CRUD Operations in Entity Framework Core. We will
work with the same example we have worked on so far.
Entity States in Entity Framework Core (EF Core)
The Entity Lifecycle in Entity Framework Core describes how an Entity is created, added,
modified, deleted, etc. Entities have many states during their lifetime. EF Core maintains the
state of each entity during its lifetime. Each entity has a state based on the operation
performed via the context class (the class derived from the DbContext class).
Each entity tracked by the DbContext has a state, which indicates how the entity should be
processed during a SaveChanges() operation. The entity state is represented by an enum
called EntityState in EF Core with the following signature.
561

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
The Entity State represents the state of an entity. An entity is always in any one of the
following states.
1. Added: The entity is tracked by the context but does not yet exist in the
database.
2. Deleted: The entity is tracked by the context and exists in the database. It
has been marked for deletion from the database but has not yet been
deleted.
3. Modified: The entity is tracked by the context and exists in the database.
Some or all of its property values have been modified but not updated in the
database.
4. Unchanged: The entity is tracked by the context and exists in the database.
Its property values have not changed from those in the database.
5. Detached: The entity is not being tracked by the context.
The Context object not only holds the reference to all the entity objects as soon as retrieved
from the database but also keeps track of entity states and maintains modifications to the
entity’s properties. This feature is known as Change Tracking.
What is Change Tracking?
Entity Framework Core (EF Core) uses the concept called change tracking to determine
what operations (like insert, update, or delete) should be executed when the
SaveChanges() method is called. It uses something called “Entity States” to determine what
operations to be done. Each entity tracked by the DbContext has a state, which indicates
how the entity should be processed during a SaveChanges() operation.
Entity Lifecycle in Entity Framework Core
When working with Entity Framework Core (EF Core), it’s important to understand the entity
lifecycle clearly. The entity lifecycle involves different states and transitions that determine
how entities interact with the DbContext and, ultimately, to the database. Understanding the
Entity lifecycle is crucial for maintaining data integrity in your applications.
562

The change in the entity state from the Unchanged to the Modified state is the only state
automatically handled by the context class. All other changes must be made explicitly using
the proper DbContext class methods. The following diagram shows the different states of an
entity in the Entity Framework.

Added State of an Entity in EF Core with Example


Whenever we add a new Entity to the context object using the Add method of DbSet or
DbContext, then the state of the entity will be in the Added state. Added entity state
indicates that the entity exists in the context but does not exist in the database. In this case,
DbContext generates the INSERT SQL Statement and inserts the data into the database
when the SaveChanges method is invoked. Once the SaveChanges method execution is
successful, the state of the Entity is changed from Added to Unchanged state.
For a better understanding, please have a look at the following example. The following
example code is self-explained, so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

//Create a new student which you want to add to the database


563

var newStudent = new Student()

FirstName = "Pranaya",

LastName = "Rout",

DateOfBirth = new DateTime(1988, 02, 29),

Height = 5.10m,

Weight = 72

};

//Create DBContext object

using var context = new EFCoreDbContext();

//Add Student Entity into Context Object

context.Students.Add(newStudent);

//context.Add<Student>(newStudent);

//context.Add(newStudent);

//Now the Entity State will be in Added State

Console.WriteLine($"Before SaveChanges Entity State:


{context.Entry(newStudent).State}");

//Call SaveChanges method to save student entity into database

context.SaveChanges();

//Now the Entity State will change from Added State to Unchanged State

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(newStudent).State}");

Console.WriteLine("Student Saved Successfully...");

catch (Exception ex)


564

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
Output:

If you verify the database, you will see the above entity is being added to the Students
database table, as shown below. Please note the Student ID, which we will use in our next
examples.

Unchanged State of an Entity in Entity Framework with Example


Once we retrieve the entity from the database, it will be in the UnChanged state as long as
we haven’t modified the entity’s property. When we call theSaveChanges, it ignores this
entity. This is the default state the entities will be in when we perform the query and attach
an entity to the context using the Attach() method.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

{
565

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();

var student = context.Students.Find(2);

if (student != null)

//Check the Entity State Before Modifying

Console.WriteLine($"Before SaveChanges Entity State: {context.Entry(student).State}");

//Calling SaveChanges Doesnot Do Anything

context.SaveChanges();

//Check the Entity State After Calling the SaveChanges Method of Context Object

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

else

Console.WriteLine("Student Not Exists");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;
566

Console.ReadKey();

}
Output:

Attaching an Existing Entity to the Context Object:


If you have an entity that already exists in the database but is not currently being tracked by
the context, then you can tell the context to track the entity using the Attach method on the
DbSet or DbContext object. The entity will be in the Unchanged state in the context. For a
better understanding, please have a look at the following example. The following example
code is self-explained, so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();


567

//Create an Existing student which you want to add to the database

var existingStudent = new Student()

StudentId = 2,

FirstName = "Pranaya",

LastName = "Rout",

DateOfBirth = new DateTime(1988, 02, 29),

Height = 5.10m,

Weight = 72

};

//Attaching the Entity to the Context

context.Students.Attach(existingStudent);

//context.Attach<Student>(existingStudent);

//context.Attach(existingStudent);

//Or you can also attach an existing entity as follows by updating the entity state

//context.Entry(existingStudent).State = EntityState.Unchanged;

//Check the Entity State Before SaveChanges

Console.WriteLine($"Before SaveChanges Entity State:


{context.Entry(existingStudent).State}");

context.SaveChanges();

//Check the Entity State After Calling the SaveChanges Method of Context Object

Console.WriteLine($"After SaveChanges Entity State:


{context.Entry(existingStudent).State}");

}
568

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
Output:

Attaching a New Entity to the Context


If you have a new entity and if you attach the entity to the context object using the DbSet or
DbContext attach method, then the context object will track the entity with Added state only,
not Unchanged state. Once you call the SaveChanges method, It will insert that entity into
the database and change the state from Added to Unchanged. For a better understanding,
please have a look at the following example. The following example code is self-explained,
so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

{
569

//Create DBContext object

using var context = new EFCoreDbContext();

//Create an Existing student which you want to add to the database

var existingStudent = new Student()

FirstName = "Rakesh",

LastName = "Kumar",

DateOfBirth = new DateTime(1988, 02, 29),

Height = 5.10m,

Weight = 72

};

//Attaching the Entity to the Context

context.Students.Attach(existingStudent);

//context.Attach<Student>(existingStudent);

//context.Attach(existingStudent);

//Or you can also attach an existing entity as follows by updating the entity state

//context.Entry(existingStudent).State = EntityState.Unchanged;

//Check the Entity State Before SaveChanges

Console.WriteLine($"Before SaveChanges Entity State:


{context.Entry(existingStudent).State}");

context.SaveChanges();

//Check the Entity State After Calling the SaveChanges Method of Context Object

Console.WriteLine($"After SaveChanges Entity State:


{context.Entry(existingStudent).State}");
570

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
Output:

Detached State of an Entity in Entity Framework Core with Example


We can change the Entity State to the Detached state by using the Entity State property.
Once the entity is in the Detached state, it will no longer be tracked by the Context object.
Later, if you want, then again, you have to use the Attach() method for the entity to be
tracked by the Context. The Detached entity state indicates that the context is not tracking
the entity.
For a better understanding, please have a look at the following example. The following
example code is self-explained, so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

{
571

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();

//Fetch the Student whose ID = 2

var student = context.Students.Find(2);

if (student != null)

//Check the Entity State Before Modifying

Console.WriteLine($"Before Detached Entity State: {context.Entry(student).State}");

//Changing the Entity State to Detached

context.Entry(student).State = EntityState.Detached;

Console.WriteLine($"Entity State After Detached: {context.Entry(student).State}");

//Change of the Property

//The following changes will have no impact on the database

student.LastName = "LastName Changed";

//SaveChanges will not do anything with Detached state Entity

context.SaveChanges();

//Check the Entity State After Calling the SaveChanges Method of Context Object

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

else
572

Console.WriteLine("Student Not Exists");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
Output:

Modified State in Entity Framework Core (EF Core) with Example


The entity will be in a Modified state whenever we modify the scalar properties of an entity.
The Modified state of an entity indicates that the entity is modified but not updated in the
database. It also indicates that the entity exists in the database. The DbContext generates
the UPDATE SQL Statement to update the entity in the database. Once the SaveChanges
is successful, the entity’s state is changed from Modified to Unchanged. For a better
understanding, please have a look at the following example. The following example code is
self-explained, so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

{
573

public class Program

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();

//Fetch the Student whose ID = 2

var student = context.Students.Find(2);

if (student != null)

Console.WriteLine($"Before Modifying Entity State: {context.Entry(student).State}");

//Modify the Entity

student.FirstName = "FirstName Changed";

Console.WriteLine($"After Modifying Entity State: {context.Entry(student).State}");

//Update the Entity in the Database by calling the SaveChanges Method

context.SaveChanges();

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

else

Console.WriteLine("Student Not Exists");

}
574

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
Output:

Note: The Entity framework keeps track of the properties that have been modified. The
Columns in the Update statement are set for only those columns whose values are
modified.
Deleted State in EF Core with Example
The entity will be removed from the context and marked as a “Deleted” state whenever we
call the Remove method. When the SaveChanges method is called, the corresponding row
is deleted from the database.
The Deleted entity state indicates that the entity is marked for deletion but not yet deleted
from the database. It also indicates that the entity exists in the database. The DbContext
generates the DELETE SQL statement to remove the entity from the database. The entity is
removed from the context once the delete operation succeeds after the SaveChanges.
For a better understanding, please have a look at the following example. The following
example code is self-explained, so please go through the comment line.
using EFCoreCodeFirstDemo.Entities;
575

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

//Create DBContext object

using var context = new EFCoreDbContext();

//Fetch the Student whose ID = 2

var student = context.Students.Find(2);

if (student != null)

Console.WriteLine($"Before DELETING Entity State: {context.Entry(student).State}");

//Remove the Entity

context.Students.Remove(student);

//context.Remove<Student>(student);

//context.Remove(student);

Console.WriteLine($"After DELETING Entity State: {context.Entry(student).State}");

//Delete the Entity in the Database by calling the SaveChanges Method

context.SaveChanges();

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");


576

else

Console.WriteLine("Student Not Exists");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

Console.ReadKey();

}
Output:

What SaveChanges Method Does in Entity Framework Core?


The SaveChanges method of the context object does different things for entities in different
states. They are as follows:
1. If the entities are in an Unchanged State, then the SaveChanges method will
not touch them. This makes sense because the entities are not modified,
added, or deleted. Then why should they be sent to the database?
2. If the entities are in the Added State, then those entities are inserted into the
database when the SaveChanges method is called by generating and
577

executing the INSERT SQL Statement, and once the SaveChanges method
execution is successful, then those entities are moved from Added to the
Unchanged state.
3. If the entities are in Modified State, then those entities are updated in the
database when the SaveChanges method is called by generating and
executing the UPDATE SQL Statement. Once the SaveChanges method
execution is successful, those entities are moved from Modified to
Unchanged.
4. If the entities are in Deleted State, then those entities are deleted from the
database when the SaveChanges method is called by generating and
executing the DELETE SQL Statement. Then, they are detached from the
context object, i.e., their state is no longer maintained by the context object.
Note: The point that you need to remember is Entity Framework builds and executes the
INSERT, UPDATE, and DELETE commands based on the state of an entity when the
SaveChanges() method is called. It generates and executes the INSERT command for the
entities with the Added state, generates and executes the UPDATE command for the
entities with the Modified state, and generates and executes the DELETE command for the
entities in the Deleted state.

LINQ to Entities in Entity Framework Core


In this article, I will discuss LINQ to Entities in Entity Framework Core (EF Core). Please
read our previous article discussing Entity States in Entity Framework Core. We will work
with the same example we have worked on so far.
LINQ to Entities in EF Core:
LINQ (Language Integrated Query) is a powerful feature in .NET that allows developers to
write queries directly using C# (or VB.NET) language. When working with Entity Framework
Core (EF Core), we can use LINQ to query our entities in a way that feels like we are
working with in-memory objects. This approach is called “LINQ to Entities”.
Querying in Entity Framework Core using LINQ to Entities remains the same as in Entity
Framework 6.x, with more optimized SQL queries and the ability to include C#/VB.NET
functions into LINQ-to-Entities queries.
Model or Entities Used in our Application:
In our application, we are using the following Student and Standard Entities.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:31 / 03:1710 Sec
Student.cs
using Microsoft.EntityFrameworkCore.Metadata.Internal;

using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

{
578

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public DateTime? DateOfBirth { get; set; }

[Column(TypeName = "decimal(18,4)")]

public decimal Height { get; set; }

[Column(TypeName = "decimal(18,4)")]

public float Weight { get; set; }

public virtual Standard? Standard { get; set; }

}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int StandardId { get; set; }

public string? StandardName { get; set; }

public string? Description { get; set; }

public ICollection<Student>? Students { get; set; }

}
579

Context Class:
The following is our EFCoreDbContext class.
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

using Microsoft.Extensions.Options;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;";);

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)


580

//use this to configure the model

//Adding Domain Classes as DbSet Properties

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
LINQ to Entities Queries in Entity Framework Core
Please ensure the following records are inside the Students and Standards table.

Please use the below SQL Scripts to create and populate the above in the database.
USE EFCoreDB1

GO

Truncate table Students;

GO

INSERT INTO Standards VALUES('1st', 'First-Standard');

INSERT INTO Standards VALUES('2nd', 'Second-Standard');

INSERT INTO Standards VALUES('3rd', 'Third-Standard');


581

GO

INSERT INTO Students VALUES('Pranaya', 'Rout', '1988-02-29', 5.10, 72, 1);

INSERT INTO Students VALUES('Mahesh', 'Kumar', '1992-12-15', 5.11, 75, 2);

INSERT INTO Students VALUES('Hina', 'Sharma', '1986-10-20', 5.5, 65, 3);

GO
LINQ-to-Entities Queries in Entity Framework Core:
The Entity Framework Core DbSet class is derived from the IQuerayable interface. As the
DbSet class is derived from the IQuerayable interface, we can use LINQ (Language
Integrated Query) for querying against DbSet, and the DbSet then covert the queries into
the SQL queries that the underlying database can understand and execute and gets the
result set, converts it into appropriate entity type objects and returns it as a query result.
What is LINQ?
The LINQ (Language Integrated Query) is part of a language but not a complete language.
Microsoft introduced LINQ with .NET Framework 3.5 and C# 3.0, available in
the System.Linq namespace.
LINQ provides a Common Query Syntax that allows us to query the data from various
sources. Using a single query, we can get or set the data from various data sources such as
SQL Server database, XML documents, ADO.NET Datasets, and other in-memory objects
such as Collections, Generics, etc. Please read the Architecture of LINQ article.
What is Projection?
Projection is nothing but the mechanism which is used to select the data from a data
source. You can select the data in the same form (i.e., the original data in its original state).
It is also possible to create a new form of data by performing some operations on it.
For example, in the database table, there is a Salary column. If you want, you can get the
Salary Column Values as it is, i.e., in their original form. But, if you want to modify the
Salary column values by adding a bonus amount of 10,000, then it is also possible. Don’t
worry if this is unclear now; we will understand this with real-time examples.
LINQ Query Methods:
The LINQ Standard Query Methods are implemented as Extension Methods, and those
methods can be used with LINQ-to-Entities queries. The following are examples of some of
the standard LINQ Query methods.
1. First() or FirstOrDefault()
2. Single()or SingleOrDefault()
3. ToList()
4. Count()
5. Min() and Max()
6. Sum()
7. Distinct()
8. Last() or LastOrDefault()
9. Average(), and many more
582

Note: Besides the above LINQ Extension methods, you can use the DbSet Find() method
to search an entity based on the primary key value.
DbSet Find() Method in Entity Framework Core:
The Find method belongs to the DbSet class. This method finds an entity with the given
primary key values. Suppose an entity with the given primary key values exists in the
context object. In that case, it is returned immediately without requesting the database, i.e.,
EF Core implements the first-level caching. If the value does not exist in the context object,
a request is made to the database to fetch the entity with the given primary key values, and
this entity, if found, is attached to the context and returned. Null is returned if no entity is
found in the context object or the database.
Example to Understand DbSet Find() Method in EF Core:
In our example, EFCoreDbContext is our DbContext class, and Students is the DbSet
property, and we want to find the student whose ID is 1. Here, StudentId is the primary key
in our Students table. So, modify the Main method of the Program class as follows.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using var context = new EFCoreDbContext();

//It will return the data from the database by executing SELECT SQL Statement

var student = context.Students.Find(1);

Console.WriteLine($"FirstName: {student?.FirstName}, LastName: {student?.LastName}");

//It will return the data from the context object as the context object tracking the same data

var student2 = context.Students.Find(1);

Console.WriteLine($"FirstName: {student2?.FirstName}, LastName:


{student2?.LastName}");
583

//It will return the data from the database by executing SELECT SQL Statement

var student3 = context.Students.Find(2);

Console.WriteLine($"FirstName: {student3?.FirstName}, LastName:


{student3?.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
First or FirstOrDefault Method in EF Core
These two methods are used when we want to fetch a single entity from the list of entities.
For example, it might be possible that there is more than one student with the FirstName
Pranaya, but our requirement is only to fetch the first student whose FirstName is Pranaya.
In this case, we need to use the First or FirstOrDefault method, as shown in the example
below. Again, we can write the LINQ queries in two different ways, i.e., using Method
Syntax and using Query Syntax,
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try
584

using var context = new EFCoreDbContext();

//FirstOrDefault method using Method Syntax

var student1 = context.Students.FirstOrDefault(s => s.FirstName == "Pranaya");

Console.WriteLine($"FirstName: {student1?.FirstName}, LastName:


{student1?.LastName}");

//First method using Method Syntax

var student2 = context.Students.First(s => s.FirstName == "Pranaya");

Console.WriteLine($"FirstName: {student2?.FirstName}, LastName:


{student2?.LastName}");

//FirstOrDefault method using Query Syntax

var student3 = (from s in context.Students

where s.FirstName == "Pranaya"

select s).FirstOrDefault();

Console.WriteLine($"FirstName: {student3?.FirstName}, LastName:


{student3?.LastName}");

//First method using Query Syntax

var student4 = (from s in context.Students

where s.FirstName == "Pranaya"

select s).First();

Console.WriteLine($"FirstName: {student4?.FirstName}, LastName:


{student4?.LastName}");

catch (Exception ex)

{
585

Console.WriteLine($"Error: {ex.Message}"); ;

}
Differences Between First and FirstOrDefault Methods in LINQ:
First and FirstOrDefault methods in LINQ return the first element from a data source. But if
the data source is null or the specified condition does not return any data, then the First
method will throw an exception while the FirstOrDefault method will not throw an exception;
instead, it returns a default value based on the element’s data type.
In our database, the Students table does not have any student with the name Smith, and if
we try to fetch the student whose FirstName is Smith using the LINQ First method, then it
will throw an exception, as shown in the below example. But, if we use the FirstOrDefault
method, it will not throw an exception; rather, it will return the default value of the Student
type, i.e., null.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using var context = new EFCoreDbContext();

//FirstOrDefault method using Method Syntax

var student1 = context.Students.FirstOrDefault(s => s.FirstName == "Smith");

Console.WriteLine($"FirstName: {student1?.FirstName}, LastName:


{student1?.LastName}");
586

//First method using Method Syntax

var student2 = context.Students.First(s => s.FirstName == "Smith");

Console.WriteLine($"FirstName: {student2?.FirstName}, LastName:


{student2?.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output: Error: Sequence contains no elements
Parameterized Query in Entity Framework Core:
The Entity Framework Core can build and execute a parameterized query in the database if
the LINQ-to-Entities query uses parameters. For a better understanding, please have a look
at the following example. In the below example, we use the parameter FirstName.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

{
587

//Creating an Instance of Context class

using var context = new EFCoreDbContext();

//Creating a Variable

string FirstName = "Pranaya";

//Using the Variable

var student = context.Students

.FirstOrDefault(s => s.FirstName == FirstName);

Console.WriteLine($"FirstName: {student?.FirstName}, LastName: {student?.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Run the above code, and you should get the following output. Please observe the
generated SELECT SQL Statement; it uses the parameter @__FirstName_0.

LINQ ToList Method:


The ToList Method is used to create a System.Collections.Generic.List<T> collection.
This method causes the query to be executed immediately. For example, if you want to list
all the students in a collection of type List, then you need to use the ToList() method, as
shown in the below example.
using EFCoreCodeFirstDemo.Entities;
588

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Creating an Instance of Context class

using var context = new EFCoreDbContext();

//Fetching All the Students from Students table

var studentList = context.Students.ToList();

//Displaying all the Student information

foreach (var student in studentList)

Console.WriteLine($"FirstName: {student?.FirstName}, LastName: {student?.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
589

}
Note: Along with ToList, you can also use ToArray, ToDictionary, or ToLookup per your
business needs.
Note: Deferred execution is a key feature of LINQ. The query is executed only when
iterating over the query results. Methods like ToList(), ToArray(), Count(), and First() cause
the query to execute immediately.
LINQ OrderBy Method:
We use the OrderBy method with ascending/descending keywords in the LINQ query to get
the sorted entity list. In simple terms, we can say that Ordering is a process to manage the
data in a particular order. It does not change the data or output. Rather, this operation
arranges the data in a particular order, i.e., ascending or descending. In this case, the count
will be the same, but the order of the elements will change.
In the following example, we sorted the students based on their First Names. Here, I am
showing you how to use Order By using the Method and Query syntax.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Creating an Instance of Context class

using var context = new EFCoreDbContext();

//Order By using Query syntax

var studentsQS = from s in context.Students

orderby s.FirstName ascending

select s;

//Order By using Method syntax


590

var studentsMS = context.Students.OrderBy(s => s.FirstName).ToList();

//Displaying the Records

foreach (var student in studentsQS)

Console.WriteLine($"\tFirstName: {student?.FirstName}, LastName:


{student?.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Anonymous Object Result:
Instead of projecting the data to a particular type like Student, we can also project the data
to an anonymous type using LINQ-to-Entities queries. Here, you may choose some or all of
an entity’s properties.
The following example returns a list of anonymous objects that contain only the FirstName,
LastName, and Height properties. The projection Result in the example below will be
anonymous because no class/entity has these properties. So, the compiler will mark it as an
anonymous type.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

{
591

static void Main(string[] args)

try

//Creating an Instance of Context class

using var context = new EFCoreDbContext();

// Query Syntax to Project the Result to an Anonymous Type

var selectQuery = (from std in context.Students

select new

firstName = std.FirstName,

lastName = std.LastName,

height = std.Height

});

//Displaying the Record

foreach (var student in selectQuery)

Console.WriteLine($"FirstName: {student?.firstName}, LastName: {student?.lastName},


Height: {student?.height}");

//Method Syntax to Project the Result to an Anonymous Type

var selectMethod = context.Students.

Select(std => new

{
592

firstName = std.FirstName,

lastName = std.LastName,

height = std.Height

}).ToList();

//Displaying the Record

foreach (var student in selectMethod)

Console.WriteLine($"FirstName: {student?.firstName}, LastName: {student?.lastName},


Height: {student?.height}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
LINQ Join Method:
The LINQ Inner join returns only the matching records from both the data sources while the
non-matching elements are removed from the result set. So, if you have two data sources,
let us say Students and Standards, and when you perform the LINQ inner join, only the
matching records, i.e., the records in both Students and Standards tables, are included in
the result set. For a better understanding, please have a look at the following example.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

{
593

internal class Program

static void Main(string[] args)

try

//Creating an Instance of Context class

using var context = new EFCoreDbContext();

//Join using Method Syntax

var JoinUsingMS = context.Students //Outer Data Source

.Join(

context.Standards, //Inner Data Source

student => student.Standard.StandardId, //Inner Key Selector

standard => standard.StandardId, //Outer Key selector

(student, standard) => new //Projecting the data into an anonymous type

StudentName = student.FirstName + " " + student.LastName,

StandrdId = standard.StandardId,

StandardDescriptin = standard.Description,

StudentHeight = student.Height

}).ToList();

foreach (var student in JoinUsingMS)

{
594

Console.WriteLine($"Name: {student?.StudentName}, StandrdId: {student?.StandrdId},


Height: {student?.StudentHeight}");

//Join using Query Syntax

var JoinUsingQS = (from student in context.Students

join standard in context.Standards

on student.Standard.StandardId equals standard.StandardId

select new

StudentName = student.FirstName + " " + student.LastName,

StandrdId = standard.StandardId,

StudentHeight = student.Height

}).ToList();

foreach (var student in JoinUsingQS)

Console.WriteLine($"Name: {student?.StudentName}, StandrdId: {student?.StandrdId},


Height: {student?.StudentHeight}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
595

}
Note: You need to remember that there are no differences in the generated SQL whether
you use Method syntax or Query Syntax using LINQ to Entities query.
Entity Framework Core New Features for Querying:
Let us learn the new querying features introduced in Entity Framework Core.
C#/VB.NET Functions in Queries
Entity Framework Core has a new feature in LINQ-to-Entities where we can include C# or
VB.NET functions in the query. This was not possible in EF 6. For a better understanding,
please have a look at the following example.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Creating an Instance of Context class

using var context = new EFCoreDbContext();

var studentsWithSameName = context.Students

.Where(s => s.FirstName == GetName())

.ToList();

foreach (var student in studentsWithSameName)

Console.WriteLine($"FirstName: {student?.FirstName}, LastName: {student?.LastName},


StandardId: {student?.Standard?.StandardId}");
596

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static string GetName()

return "Pranaya";

}
In the above example, we have included the GetName() C# function in the Where clause.
Run the above code, and you should get the following output.

Note: LINQ to Entities in EF Core provides a convenient, type-safe way to create database
queries. EF Core then translates the queries into corresponding SQL queries based on the
EF Core Database Provider and backend database. However, it’s important to consider
performance issues like the N+1 query problem and optimize complex queries by examining
the generated SQL.

Eager Loading in Entity Framework Core (EF Core)


In this article, I will discuss Eager Loading in Entity Framework Core (EF Core) with
Examples. Please read our previous article discussing LINQ to Entities Queries in EF
Core. At the end of this article, you will learn how to load the entities eagerly. You will also
learn how to Eager Loading from multiple Levels and Tables with Examples. We will work
with the same example we have worked on so far.
597

What is Eager Loading in EF Core?


Eager loading in Entity Framework Core is a technique used to load related data of an entity
as part of the initial query. It’s an efficient way to retrieve all necessary data in a single
database query, avoiding the need for multiple round-trip calls to the database. This is
useful when you know you need related data for each entity and want to optimize
performance by minimizing the number of queries. Eager loading is achieved using the
Include and ThenInclude methods within a LINQ query:
Examples to Understand Eager Loading in Entity Framework Core
Now, to understand Eager Loading in Entity Framework Core, we need multiple database
tables. So, we are going to add some new database tables as well as going to update the
existing database table.
Entities:
We are going to use the following Entities in our example. We will update the Student and
Standard Entities while adding the rest.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:35 / 03:1710 Sec
Student.cs
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int? StandardId { get; set; }

public virtual Standard? Standard { get; set; }

public virtual StudentAddress? StudentAddress { get; set; }

public virtual ICollection<Course> Courses { get; set; } = new List<Course>();

}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities
598

public class Standard

public int StandardId { get; set; }

public string? StandardName { get; set; }

public string? Description { get; set; }

public virtual ICollection<Student> Students { get; set; } = new List<Student>();

public virtual ICollection<Teacher> Teachers { get; set; } = new List<Teacher>();

}
StudentAddress.cs
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class StudentAddress

[Key]

public int StudentId { get; set; }

public string? Address1 { get; set; }

public string? Address2 { get; set; }

public string? Mobile { get; set; }

public string? Email { get; set; }

public virtual Student Student { get; set; } = null!;

}
599

}
Course.cs
namespace EFCoreCodeFirstDemo.Entities

public class Course

public int CourseId { get; set; }

public string? CourseName { get; set; }

public int? TeacherId { get; set; }

public virtual Teacher? Teacher { get; set; }

public virtual ICollection<Student> Students { get; set; } = new List<Student>();

}
Teacher.cs
namespace EFCoreCodeFirstDemo.Entities

public class Teacher

public int TeacherId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int? StandardId { get; set; }

public virtual ICollection<Course> Courses { get; set; } = new List<Course>();

public virtual Standard? Standard { get; set; }

}
600

}
Modifying the Context Class:
Modify the EFCoreDbContext class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)


601

//Use this to configure the model using Fluent API

//Adding Domain Classes as DbSet Properties

public virtual DbSet<Course> Courses { get; set; }

public virtual DbSet<Standard> Standards { get; set; }

public virtual DbSet<Student> Students { get; set; }

public virtual DbSet<StudentAddress> StudentAddresses { get; set; }

public virtual DbSet<Teacher> Teachers { get; set; }

}
Note: As we already discussed, whenever we add or update domain classes or
configurations, we need to sync the database with the model using add-
migration and update-database commands using Package Manager Console or .NET
Core CLI. In Visual Studio, open the NuGet Package Manager Console. To launch Package
Manager Console, select Tools => NuGet Package Manager => Package Manager
Console from the menu below.
602

This will open the Package Manager Console. Now, type the add-migration
CreateEFCoreDB2 command, select the project where your Context class is, and press the
enter button, as shown in the image below.

You will get the following message once the above command is executed
successfully.

We are getting one warning that an operation was scaffolded, possibly resulting in data
loss. Please review the migration for accuracy. So, he is saying that before updating the
database, please review the migration because there might be some data loss. You can
ignore this at this moment.
Once this command is executed successfully, you will see that another migration class file
will be created with the current date and time, along with the name you provided in the
603

migration command. You can see this class file within the Migrations folder, as shown in the
below image.

Don’t think the database is created after creating the migration file using the add-migration
command. We still need to update the database using the Update-Database command. We
can use the –verbose option to view the SQL statements executed in the target database.
So, open the Package Manager Console and execute the Update-Database –
verbose command, as shown in the image below.

Once the above script is executed successfully, you can verify the database table using
SSMS, as shown in the image below.
604

Eager Loading in EF Core:


So, to understand Eager Loading in EF Core, we need to have some data in the database
table. So, please execute the following SQL Script to insert some dummy data into the
respective database tables using SSMS.
-- Use EFCoreDB1

USE EFCoreDB1;

GO

-- Standards table data

INSERT INTO Standards VALUES('STD1', 'Outstanding');

INSERT INTO Standards VALUES('STD2', 'Good');

INSERT INTO Standards VALUES('STD3', 'Average');

INSERT INTO Standards VALUES('STD4', 'Below Average');

GO
605

-- Teachers table data

INSERT INTO Teachers VALUES('Anurag', 'Mohanty', 1);

INSERT INTO Teachers VALUES('Preety', 'Tiwary', 2);

INSERT INTO Teachers VALUES('Priyanka', 'Dewangan', 3);

INSERT INTO Teachers VALUES('Sambit', 'Satapathy', 3);

INSERT INTO Teachers VALUES('Hina', 'Sharma', 2);

INSERT INTO Teachers VALUES('Sushanta', 'Jena', 1);

GO

-- Courses table data

INSERT INTO Courses VALUES('.NET', 1);

INSERT INTO Courses VALUES('Java', 2);

INSERT INTO Courses VALUES('PHP', 3);

INSERT INTO Courses VALUES('Oracle', 4);

INSERT INTO Courses VALUES('Android', 5);

INSERT INTO Courses VALUES('Python', 6);

GO

-- Students table data

INSERT INTO Students VALUES('Pranaya', 'Rout', 1);

INSERT INTO Students VALUES('Prateek', 'Sahu', 2);

INSERT INTO Students VALUES('Anurag', 'Mohanty', 3);

INSERT INTO Students VALUES('Hina', 'Sharma', 4);

GO

-- StudentAddresses table data


606

INSERT INTO StudentAddresses VALUES(1, 'Lane1', 'Lane2', '1111111111',


'1@dotnettutorials.net');

INSERT INTO StudentAddresses VALUES(2, 'Lane3', 'Lane4', '2222222222',


'2@dotnettutorials.net');

INSERT INTO StudentAddresses VALUES(3, 'Lane5', 'Lane6', '3333333333',


'3@dotnettutorials.net');

INSERT INTO StudentAddresses VALUES(4, 'Lane7', 'Lane8', '4444444444',


'4@dotnettutorials.net');

GO

-- StudentCourse table data

INSERT INTO CourseStudent VALUES(1,1);

INSERT INTO CourseStudent VALUES(2,1);

INSERT INTO CourseStudent VALUES(3,2);

INSERT INTO CourseStudent VALUES(4,2);

INSERT INTO CourseStudent VALUES(1,3);

INSERT INTO CourseStudent VALUES(6,3);

INSERT INTO CourseStudent VALUES(5,4);

INSERT INTO CourseStudent VALUES(6,4);

GO
How many ways can we load the Related Entities in Entity Framework
Core?
In Entity Framework Core, we can load the related entities in three ways. They are Eager
Loading, Lazy Loading, and Explicit Loading. All these three techniques, i.e., Eager
Loading, Lazy Loading, and Explicit Loading, refer to loading the related entities of the main
entity. The only difference is they define when to load the related or child entities of the
main entity.
First, we must understand what is related or child entities in the Entity Framework Core. For
a better understanding, please look at the following Student Entity. Inside this Student
Entity, we have three navigation properties, i.e., Standard and StudentAddress as
Reference Navigation Property and Courses as Collection Navigation Property. These
607

Navigation properties are nothing but pointing to some other Entities. These other entities
are nothing but the related entities of the Student Entity. Related entities are entities created
using Foreign Keys.

When loading the Student entity, i.e., retrieving the data from the Student database table,
how can we retrieve the related entities, i.e., Standard, StudentAddress, and Courses? We
can load these related entities in three ways while retrieving the Student entity. They
are Eager Loading, Lazy Loading, and Explicit Loading. In this article, we will discuss
Eager Loading in detail, and in our next two articles, we will discuss Lazy
Loading and Explicit Loading with Examples.
What is Eager Loading in Entity Framework Core?
Eager Loading in Entity Framework Core loads related entities whenever we load the main
entity in a single query. This helps reduce the number of database queries or round-trips
and improves performance by retrieving all necessary data in one go. This approach is
efficient as it saves bandwidth and server CPU time. Eager loading is especially useful
when we want to access properties of related entities without triggering additional database
requests. Entity Framework Core achieves this by using JOINs to load the related entities
instead of separate SQL queries.
How to Implement Eager Loading in EF Core?
Entity Framework Core supports Eager Loading of related entities, same as EF 6, using
the Include() extension method. In addition to this, it also provides
the ThenInclude() extension method to load multiple levels of related entities. (EF 6 does
not support the ThenInclude() method)
Example to Understand Eager Loading in EF Core:
What our requirement is, when loading the Student entities, we also need to eager-load the
corresponding Standard entities. The students and their Standards will be retrieved in a
single query using the SQL join.
In the following example, we are fetching all the students from the Student database table
and its standards using the DbSet Include() method. As shown in the example, we can
specify a lambda expression as a parameter in the Include() method to specify a navigation
property. To use the Include method, we need to
608

import Microsoft.EntityFrameworkCore namespace. In the example below, I show how to


specify the related entity name using string name and lambda expression using Method and
Query syntax.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

var context = new EFCoreDbContext();

//Loading Related Entities using Eager Loading

//While Loading the Student table data, it also load the Standard table data

//Eager Loading using Method Syntax

//Specifying the Related Entity Name using Lambda Expression

var students = context.Students

.Include(std => std.Standard)

.ToList();

//Specifying the Related Entity Name using String Name

//var students = context.Students

// .Include("Standard")

// .ToList();
609

//Eager Loading using Query Syntax

//Specifying the Related Entity Name using Lambda Expression

//var students = (from s in context.Students.Include(std => std.Standard)

// select s).ToList();

//Specifying the Related Entity Name using String Name

//var students = (from s in context.Students.Include("Standard")

// select s).ToList();

//Printing all the Student and Standard details

foreach (var student in students)

Console.WriteLine($"Firstname: {student.FirstName}, Lastname: {student.LastName},


StandardName: {student.Standard?.StandardName}, Description:
{student.Standard?.Description}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:
610

As you can see in the above output, EF Core uses SQL Join to fetch the data.
Note: The Include() method with a lambda expression is recommended. This is because,
with the Include method with a string name, we will get a runtime exception if the property
name is misspelled or does not exist. So, always use the Include() method with the lambda
expression to detect the error during compile time.
FromSql() Method in EF Core:
Using the FromSql() Method in EF Core, we can specify the RAW T-SQL Statements
executed in the database. The Include() extension method in EF Core can also be used
after the FromSql() method. For a better understanding, please have a look at the following
example.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

var context = new EFCoreDbContext();

//Loading Related Entities using Eager Loading

//While Loading the Student Information, also load the related Standard data

//Create the SQL Statement using FormattableString object


611

FormattableString sql = $"Select * from Students WHERE FirstName = 'Pranaya'";

//Pass the FormattableString Object to the FromSql Method

var StudentWithStandard = context.Students

.FromSql(sql)

.Include(s => s.Standard) //Eager Load the Related Standard data

.FirstOrDefault();

//Printing the Student and Standard details

Console.WriteLine($"Firstname: {StudentWithStandard?.FirstName}, Lastname:


{StudentWithStandard?.LastName}, " +

$"StandardName: {StudentWithStandard?.Standard?.StandardName}, Description:


{StudentWithStandard?.Standard?.Description}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

How to Load Multiple Related Entities Using Include Method in EF Core?


612

If you look at the Student Entity, you will see that the Student Entity Has three related
entities as Standard, StudentAddress as Reference Navigational Entities, and Courses as
Collection Navigation Entity, as shown in the image below.

Our requirement is to load the Standard, StudentAddreess, and Courses Entities while
loading the Student Entity. Is it possible in Entity Framework Core? Yes, it is possible. We
can eagerly load multiple related entities using the Include method multiple times in Entity
Framework Core.
So, we need to use the Include() method multiple times to load multiple navigation
properties of the same entity. In the below example, we eagerly load the Student, Standard,
StudentAddress, and Courses entities while loading the Student Entity.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

{
613

var context = new EFCoreDbContext();

//Loading Related Entities using Eager Loading

//While Loading the Student table data, it is also going to load the Standard, StudentAddress
and Courses tables data

//Eager Loading Multiple Entities using Method Synatx

var students = context.Students

.Include(x => x.Standard)

.Include(x => x.StudentAddress)

.Include(x => x.Courses)

.ToList();

//Eager Loading Multiple Entities using Query Synatx

//var students = (from s in context.Students

// .Include(x => x.Standard)

// .Include(x => x.StudentAddress)

// .Include(x => x.Courses)

// select s).ToList();

//Printing all the Student and Standard details

foreach (var student in students)

Console.WriteLine($"Student Name: {student.FirstName} {student.LastName},


StandardName: {student.Standard?.StandardName}, Address:
{student.StudentAddress?.Address1}");

foreach (var course in student.Courses)

{
614

Console.WriteLine($"\tCourse ID: {course.CourseId} Course Name:


{course.CourseName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Now, run the application, and you should get the Student and all the related entities data as
expected, as shown in the below image.

ThenInclude Method in EF Core:


It is also possible to eagerly load multiple levels of related entities in Entity Framework
Core. Entity Framework Core introduced the new ThenInclude() extension method to load
multiple levels of related entities.
So, first, we need to understand what it means by multiple levels of related entities. If you
go to the Student entity or Student class, you will see that it includes one related entity
615

called Standard, i.e., it includes it as a Navigation property. Now, go to the Standard entity
or Standard class. It includes one related entity called Teachers, i.e., the Standard Entity
includes the Teachers entity as a collection navigation entity.
Now, what we want is, we want to load Student, Standard, and Teacher entities. This is
nothing but multiple levels of related entities. For a better understanding, please have a look
at the following example.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

var context = new EFCoreDbContext();

//Loading Related Entities using Eager Loading

//While Loading the Student table data, it is also going to load the Standard, StudentAddress
and Courses tables data

//Method Synatx

var student = context.Students.Where(s => s.FirstName == "Pranaya")

.Include(s => s.Standard)

.ThenInclude(std => std.Teachers)

.FirstOrDefault();

Console.WriteLine($"Firstname: {student?.FirstName}, Lastname: {student?.LastName},


StandardName: {student?.Standard?.StandardName}, Description:
{student?.Standard?.Description}");
616

if(student?.Standard != null)

//You can also access the Teacher collection here

foreach (var teacher in student.Standard.Teachers)

Console.WriteLine($"\tTeacher ID: {teacher.TeacherId}, Name: {teacher.FirstName}


{teacher.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Projection Query in EF Core:


617

We can also load multiple related entities using the projection query instead of the Include()
or ThenInclude() methods. The following example demonstrates the projection query to load
the Student, Standard, and Teacher entities. In the below example, the Select extension
method is used to include the Student, Standard, and Teacher entities in the result. This will
execute the same SQL query as ThenInclude() method.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

var context = new EFCoreDbContext();

//Loading Related Entities using Eager Loading

//While Loading the Student table data, it is also going to load the Standard, StudentAddress
and Courses tables data

var teachers = new List<Teacher>();

var std = context.Students.Where(s => s.FirstName == "Pranaya")

.Select(s => new

Student = s,

Standard = s.Standard,

Teachers = (s.Standard != null)? s.Standard.Teachers : teachers


618

})

.FirstOrDefault();

Console.WriteLine($"Firstname: {std?.Student?.FirstName}, Lastname:


{std?.Student?.LastName}, StandardName: {std?.Student?.Standard?.StandardName},
Description: {std?.Student?.Standard?.Description}");

if(std?.Student?.Standard != null)

//You can also access the Teacher collection here

foreach (var teacher in std.Student.Standard.Teachers)

Console.WriteLine($"\tTeacher ID: {teacher.TeacherId}, Name: {teacher.FirstName}


{teacher.LastName}");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:
619

Advantages of using Eager Loading in EF Core


Eager loading in Entity Framework Core provides several advantages that can improve
performance and more efficient use of database resources. Here are some of the key
advantages of using eager loading:
1. Reduced Database Round-Trips: Eager loading retrieves related data in a
single query, reducing round-trips to the database. It improves performance,
especially for complex object graphs or large datasets.
2. Improved Performance: Retrieving all necessary data in a single query
through eager loading can result in faster execution times and lower latency
than lazy loading, which fetches related data on demand.
3. Avoiding the N+1 Query Problem: Eager loading helps solve the “N+1
Query Problem,” where lazy loading can result in the “N+1 Query
Problem” when iterating over a collection of entities. Eager loading reduces
this overhead by fetching all related data in a single query.
4. Simplified Code: Eager loading simplifies code by directly accessing related
properties and collections, avoiding additional queries and leading to cleaner,
more maintainable code.
5. Optimal Use of Database Resources: Eager loading enables the database
to optimize query execution plans, potentially resulting in better utilization of
indexes and caching mechanisms.
6. Better Performance for Complex Queries: Performing queries involving
multiple levels of related data can be made easier and more efficient using
eager loading.
7. Enhanced User Experience: Faster data retrieval times can improve user
experience in applications that display data.
Disadvantages of using Eager Loading in EF Core
While Eager Loading in Entity Framework Core offers several advantages, it also comes
with certain disadvantages you should be aware of. Here are some of the key
disadvantages of using Eager loading:
1. Over Fetching of Data: It is important to note that eager loading can result in
larger query results and increased memory usage, ultimately leading to
performance issues.
2. Performance Impact for Large Data Sets: Eager loading can be inefficient
when working with large datasets or deeply nested object graphs. Retrieving
620

all related data in one query can cause performance issues, especially if the
data is not used immediately.
3. Complex Queries: Eagerly loading multiple levels of related entities can lead
to complex and potentially inefficient SQL queries, negatively impacting
database performance and query execution time.
4. Increased Initial Query Time: Eager loading can impact the application
responsiveness due to increased initial query execution time, particularly
when joining multiple tables or retrieving significant data.
5. Inflexible Loading Strategy: Eager loading loads all related data upfront,
which may not be suitable for all scenarios. It can lead to unnecessary data
retrieval when only a subset of related data is needed.
6. Increased Complexity: Although eager loading can simplify code in some
scenarios, it may also bring in unnecessary data, adding complexity.
Developers must optimize eager loading configurations carefully.

Lazy Loading in Entity Framework (EF) Core


In this article, I will discuss Lazy Loading in Entity Framework (EF) Core with Examples.
Please read our previous article discussing Eager Loading in Entity Framework Core with
Examples. At the end of this article, you will understand the following pointers:
1. What is Lazy Loading in Entity Framework Core?
2. Example to Understand Lazy Loading in Entity Framework Core
3. How to Implement Lazy Loading in EF Core?
4. Lazy Loading With Proxies in EF Core
5. Lazy Loading Without Proxies in Entity Framework Core
6. How to Disable Lazy Loading in EF Core?
7. Advantages and Disadvantages of using Lazy Loading in EF Core
8. When to use Lazy Loading in EF Core?
9. What is the Difference Between Eager Loading and Lazy Loading in EF
Core?
10. Which is good – Eager Loading or Lazy Loading in Entity Framework
Core?
What is Lazy Loading in Entity Framework Core?
Lazy Loading is a Process where Entity Framework Core loads the related entities on
demand. That means the related entities or child entities are loaded only when it is being
accessed for the first time. So, in simple words, we can say that Lazy loading delays the
loading of related entities until you specifically request it.
Lazy loading in Entity Framework Core is a pattern where related data is automatically
loaded from the database as soon as the navigation property on an entity is accessed for
the first time. This approach can simplify the code and avoid loading unnecessary data.
Example to Understand Lazy Loading in Entity Framework Core:
Let us understand Lazy Loading in EF Core with an example. Please have a look at the
following Student entity. Looking at the Student class, you will find a navigation property for
621

the StudentAddress entity. At the same time, you will also find a navigation property for
Standard and a collection navigation property for Courses Entities.
Ad
1/2
00:07

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV

In Lazy Loading, the context object first loads the Student entity data from the database.
Then, it will load the related entities when you access those navigation properties for the
first time. If you are not accessing the Navigation Properties, it will not load those related
data from the database.
For example, when executing the below statement, the context object will only load the
Student data using a SELECT SQL Statement. It will not load the related StudentAddress,
Standard, or Courses table data.
Student? student = context.Students.FirstOrDefault(std => std.StudentId == 1);
Later, suppose you execute the below statement, i.e., access the StudentAddress Property
for the First Time. In that case, the Context object will issue a SELECT SQL Statement and
load the related StudentAddress data from the database table.
StudentAddress? studentAddress = student?.StudentAddress;
Similarly, when you execute the below statement, the Context object will generate and
execute another SELECT SQL Statement to fetch the related data from the Standards
database table.
Standard? standard = student?.Standard;
How to Implement Lazy Loading in EF Core?
We can implement Lazy loading in Entity Framework Core in two ways. They are as follows:
1. Lazy Loading With Proxies
2. Lazy loading Without Proxies
Lazy Loading With Proxies in EF Core:
622

To enable Lazy Loading using Proxies in Entity Framework Core, the first step is to install
the Proxy Package provided by Microsoft. This can be done by
installing Microsoft.EntityFrameworkCore.Proxies package, which will add all the
necessary proxies. There are three steps involved in enabling Lazy Loading using Proxies.
1. Install Proxies Package
2. Enable Lazy Loading using UseLazyLoadingProxies
3. Mark the Navigation Property as a Virtual
Step 1: Install Microsoft.EntityFrameworkCore.Proxies Package
So, open NuGet Package Manager UI by selecting Tools => NuGet Package Manager =>
Manage NuGet Packages for Solution from the Visual Studio Menu, which will open the
following window. From this window, select the Search tab, then search
for Microsoft.EntityFrameworkCore.Proxies package and
select Microsoft.EntityFrameworkCore.Proxies package and click the Install button, as
shown in the image below.

Once you click the Install button, it will open the Preview Changes window to tell what
packages will be installed. Review the changes and click the OK button in the image below.
623

Once you click on the OK button, it will open the License Acceptance window. Click on the I
Accept button as shown in the below image.
624

Once you click on the I Accept button, it will install the package, and you can verify the
package inside the Dependencies => Packages folder of your project, as shown in the
image below.
625

Alternatively, you can also Install Microsoft.EntityFrameworkCore.Proxies package using


the Package Manager Console and .NET Core CLI Command as follows:
1. [Package Manager Console]: install-package
Microsoft.EntityFrameworkCore.Proxies
2. [Dotnet CLI]: dotnet add package
Microsoft.EntityFrameworkCore.Proxies
Step 2: Enable Lazy Loading using UseLazyLoadingProxies
Once you Install Microsoft.EntityFrameworkCore.Proxies package, then you need to use
the UseLazyLoadingProxies method to enable the Lazy Loading in Entity Framework Core
within the OnConfiguring method of the DbContext class.
optionsBuilder.UseLazyLoadingProxies();
So, modify the EFCoreDbContext context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Enabling Lazy Loading

optionsBuilder.UseLazyLoadingProxies();

//Configuring the Connection String


626

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Use this to configure the model using Fluent API

//Adding Domain Classes as DbSet Properties

public virtual DbSet<Course> Courses { get; set; }

public virtual DbSet<Standard> Standards { get; set; }

public virtual DbSet<Student> Students { get; set; }

public virtual DbSet<StudentAddress> StudentAddresses { get; set; }

public virtual DbSet<Teacher> Teachers { get; set; }

}
Step3: Mark the Navigation Property as Virtual
Finally, we need to mark the navigation property as virtual, which we want to Laod Lazily.
So, if you see our Student, we have already marked the Navigation Properties as Virtual, as
shown in the below image.
627

Note: If you have not marked the navigation properties as virtual, then please add the
virtual keyword, and then, as we already discussed, whenever we add or update domain
classes, we need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
Using Lazy Loading in EF Core:
 How It Works: When you access a navigation property, EF Core checks if
the related data is already loaded. If not, it automatically queries the database
to load the data.
 Usage: Lazy loading is useful when you’re unsure whether you’ll need related
data and want to avoid the overhead of loading it unnecessarily.
Now, modify the Main method of the Program class as follows to use Lazy Loading in Entity
Framework Core.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try
628

var context = new EFCoreDbContext();

//Loading the particular student data only

//Here, it will only load the Student Data, no related entities

Student? student = context.Students.FirstOrDefault(std => std.StudentId == 1);

Console.WriteLine($"Firstname: {student?.FirstName}, Lastname: {student?.LastName}");

Console.WriteLine();

//Loading the Student Address (it will execute Separate SQL query)

StudentAddress? studentAddress = student?.StudentAddress;

Console.WriteLine($"AddresLin1 {studentAddress?.Address1}, AddresLin2


{studentAddress?.Address2}");

Console.WriteLine();

//Loading the Standard (it will execute Separate SQL query)

Standard? standard = student?.Standard;

Console.WriteLine($"StandardName: {standard?.StandardName}, Description:


{standard?.Description}");

Console.WriteLine();

//Loading the Course (it will execute separate SQL query)

//var courses = student?.Courses;

//if( courses != null )

//{

// foreach (var course in courses)

// {

// Console.WriteLine($"CourseName: {course.CourseName}");
629

// }

//}

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

As you can see in the above output, it is issuing three different Select Statements to load
the data from the database. This means it is using Lazy Loading to load the related data.
Further, you can see that it is not loading the Courses table data, as we have commented
on the statement accessing the Courses navigation property.
Note: Lazy loading can lead to the N+1 problem, which causes unnecessary database
roundtrips.
Lazy Loading Without Proxies in Entity Framework Core:
The second approach to enabling Lazy Loading in Entity Framework Core is using
the ILazyLoader interface. Implementing Lazy-Loading Without Proxies in EF Core is done
by injecting the ILazyLoader service into an entity.
1. The ILazyLoader interface is responsible for loading navigation properties
that have not yet been loaded.
2. The feature can be easily integrated into the main component of the
database. That is to the Principal Entity.
630

3. To use the ILazyLoader, it is necessary to


install Microsoft.EntityFrameworkCore.Abstraction Package.
Step 1: Install Microsoft.EntityFrameworkCore.Abstraction Package
So, open NuGet Package Manager UI by selecting Tools => NuGet Package Manager =>
Manage NuGet Packages for Solution from the Visual Studio Menu, which will open the
following window. From this window, select the Search tab, then search
for Microsoft.EntityFrameworkCore.Abstraction package and
select Microsoft.EntityFrameworkCore.Abstraction package and click the Install button,
as shown in the image below.

Once you click the Install button, it will open the Preview Changes window to tell what
packages will be installed. Review the changes and click the OK button in the image below.

Once you click on the OK button, it will open the License Acceptance window. Click on the I
Accept button as shown in the below image.
631

Once you click on the I Accept button, it will install the package, and you can verify the
package inside the Dependencies => Packages folder of your project, as shown in the
image below.
632

Alternatively, you can also Install Microsoft.EntityFrameworkCore.Abstraction package


using the Package Manager Console and .NET Core CLI Command as follows:
1. [Package Manager Console]: install-package
Microsoft.EntityFrameworkCore.Abstraction
2. [Dotnet CLI]: dotnet add package
Microsoft.EntityFrameworkCore.Abstraction
Step 2: Alter the Principal Entity
Once you install Microsoft.EntityFrameworkCore.Abstraction Package: You must alter
the Principal entity to include the following.
1. A using directive for Microsoft.EntityFrameworkCore.Infrastructure
2. A private field for the ILazyLoader instance
3. An Empty Constructor and One Parameterized Constructor that takes
an ILazyLoader as a parameter to Initialize the ILazyLoader instance.
4. A Private field for the Navigation Property
5. A Public Getter and Setter property for the Private Navigation Field that uses
the ILazyLoader.Load method.
In our example, the Student Entity is the Principal Entity. Please modify the Student Entity
to include the above-discussed thing so it can load the related entities lazily.
//Adding the namespace

using Microsoft.EntityFrameworkCore.Infrastructure;

namespace EFCoreCodeFirstDemo.Entities

public class Student

//A private field for the ILazyLoader instance


633

private readonly ILazyLoader _lazyLoader;

//An empty constructor

public Student()

//one parameterized that takes an ILazyLoader as a parameter to

//Initialize the ILazyLoader instance

public Student(ILazyLoader lazyLoader)

_lazyLoader = lazyLoader;

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int? StandardId { get; set; }

//Private Field For Standard Navigation Property

private Standard _Standard;

//Public Setter and Getter Property for the Private _Standard Field

public Standard Standard

get => _lazyLoader.Load(this, ref _Standard);

set => _Standard = value;

//public virtual StudentAddress? StudentAddress { get; set; }


634

//Private Field For StudentAddress Navigation Property

private StudentAddress _StudentAddress;

//Public Setter and Getter Property for the Private _StudentAddress Field

public StudentAddress StudentAddress

get => _lazyLoader.Load(this, ref _StudentAddress);

set => _StudentAddress = value;

//Private Field For Course Collection Navigation Property

private ICollection<Course> _Courses = new List<Course>();

//Public Setter and Getter Property for the Private _Courses Field

public ICollection<Course> Courses

get => _lazyLoader.Load(this, ref _Courses);

set => _Courses = value;

}
Next, modify the EFCoreDbContext class as follows. Here, we are removing
the optionsBuilder.UseLazyLoadingProxies() method call, which enables Lazy Loading
using Proxies.
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

{
635

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Use this to configure the model using Fluent API

//Adding Domain Classes as DbSet Properties

public virtual DbSet<Course> Courses { get; set; }

public virtual DbSet<Standard> Standards { get; set; }


636

public virtual DbSet<Student> Students { get; set; }

public virtual DbSet<StudentAddress> StudentAddresses { get; set; }

public virtual DbSet<Teacher> Teachers { get; set; }

}
We don’t need to make any changes to the Main method. So, now, run the application, and
you should get the following output as expected. As you can see, EF Core uses separate
SELECT SQL Statements to load the related entities.

How to Disable Lazy Loading in EF Core?


Suppose you want to disable Lazy Loading for the whole application. In that case, you need
to use the LazyLoadingEnabled property of the ChangeTracker class and set this property
value to true within the constructor of the context class, as shown in the image below.

So, modify the EFCoreDbContext class to disable Lazy Loading for the whole application in
Entity Framework Core.
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

{
637

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//Disabling Lazy Loading

this.ChangeTracker.LazyLoadingEnabled = false;

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Use this to configure the model using Fluent API

//Adding Domain Classes as DbSet Properties


638

public virtual DbSet<Course> Courses { get; set; }

public virtual DbSet<Standard> Standards { get; set; }

public virtual DbSet<Student> Students { get; set; }

public virtual DbSet<StudentAddress> StudentAddresses { get; set; }

public virtual DbSet<Teacher> Teachers { get; set; }

}
With these changes in place, if you run the application, you will see that the related entities
will not be loaded when we access them, as shown in the image below.

Note: The default value of LazyLoadingEnabled is set to true. Although we have not set this
property value to true, Lazy Loading is still working in our application. Even if you want, then
while accessing the data, you can enable and disable lazy loading.
For a better understanding, please have a look at the following example. In the example
below, we disable the Lazy Loading using the statement context after fetching the Student
data, i.e., ChangeTracker.LazyLoadingEnabled = false; hence, it will not load the
Standard data. Then, we again enabled the Lazy loading by using
the context.ChangeTracker.LazyLoadingEnabled = true; statement, and then we are
accessing the StudentAddress navigation property, and this time, as Lazy Loading is
enabled, it will issue a separate SELECT SQL Statement and load the Student Address
data.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)


639

try

var context = new EFCoreDbContext();

//Loading the particular student data only

//Here, it will only load the Student Data, no related entities

Student? student = context.Students.FirstOrDefault(std => std.StudentId == 1);

Console.WriteLine($"Firstname: {student?.FirstName}, Lastname: {student?.LastName}");

Console.WriteLine();

//I want to Disable Lazy Loading Here

context.ChangeTracker.LazyLoadingEnabled = false;

//As Lazy Loading is Disabled so, it will not load the Standard data

Standard? standard = student?.Standard;

Console.WriteLine($"StandardName: {standard?.StandardName}, Description:


{standard?.Description}");

Console.WriteLine();

//I want to Enable Lazy Loading Here

context.ChangeTracker.LazyLoadingEnabled = true;

//As Lazy Loading is Enabled so, it will load the StudentAddress data

StudentAddress? studentAddress = student?.StudentAddress;

Console.WriteLine($"AddresLin1 {studentAddress?.Address1}, AddresLin2


{studentAddress?.Address2}");

Console.WriteLine();

}
640

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Advantages of using Lazy Loading in EF Core:


Lazy loading in Entity Framework Core (EF Core) offers several advantages that can
simplify code, improve memory usage, and provide a more efficient approach to data
retrieval. Here are some key advantages of using Lazy Loading in Entity Framework Core:
 Improved Initial Performance: Lazy loading defers the loading of related
data until it is actually needed, which can reduce the time and resources
required to load an entity initially. This can be particularly beneficial in
scenarios where only a portion of the related data is needed.
 Simplified Data Access Code: Lazy loading simplifies the data access code
by automatically loading related entities. This means developers do not need
to include related entities in their queries explicitly. It can lead to more
readable and maintainable code, as the logic for loading related data is
abstracted away.
 Dynamic Data Retrieval: Only the data that is actually used is loaded, which
can be efficient in scenarios where the complete dataset is not needed
upfront.
 Flexibility: It provides flexibility in accessing related data – the application
can navigate through relationships in the object graph without having to pre-
plan all data loading in advance.
641

 Potentially Lower Memory Usage: In scenarios where a small portion of


related data is used, lazy loading can help keep the memory footprint low by
not loading unused data.
 Useful in Certain Scenarios: In complex models with deep object graphs,
lazy loading can prevent the unnecessary loading of vast amounts of related
data, which might never be used.
 User-Driven Data Access: In user-interactive applications where the user
navigates and drives data access, lazy loading can improve responsiveness
and reduce resource consumption.
Disadvantages of using Lazy Loading in EF Core
While lazy loading in Entity Framework Core (EF Core) offers certain benefits, it also has
disadvantages and pitfalls. Here are some key disadvantages of using lazy loading:
 N+1 Query Problem: Lazy loading can lead to the N+1 query problem,
where accessing each object in a collection results in a separate query to the
database. This can significantly impact performance, especially in scenarios
with large datasets.
 Unintended Queries: It’s easy to unintentionally trigger database queries
without realizing it, especially when accessing properties in a loop or during
serialization.
 Hidden Database Calls: Because database queries are made implicitly,
tracking and debugging them can be harder. This can make performance
tuning and debugging more challenging.
 Testing Complexity: Writing unit tests can be more complex since you need
to account for the database calls that are made implicitly.
 Potential for Increased Memory Usage: Lazy loading can lead to increased
memory consumption if not managed carefully, especially when large
amounts of data are loaded into memory that might not be necessary.
 Tight Coupling with Entity Framework: Entities often need to be aware of
the context (e.g., by including virtual properties for navigation), which can
lead to a tighter coupling between your domain models and EF Core.
 Unpredictable Behavior in Disconnected Scenarios: In scenarios where
the context is disposed (like in Web applications or Web APIs), lazy loading
can lead to exceptions when accessing navigation properties, as the context
is no longer available to load related data.
 Risk of Data Inconsistencies: In long-running contexts, lazy loading can
result in data inconsistencies if related data changes in the database after the
initial data is loaded but before the lazy load occurs.
 Increased Complexity in Transaction Management: Managing
transactions can be more complex with lazy loading, as the boundaries of
database interactions are less clear.
What is the Difference Between Eager Loading and Lazy Loading
in EF Core?
In Entity Framework Core, eager and lazy loading are techniques used to load related data
from the database, but they differ significantly in how and when the data is loaded.
Eager Loading in EF Core:
642

Eager loading is the process of loading the main entity and its related entities from the
database in a single query. This is typically done using the Include and ThenInclude
methods in a LINQ query.
Characteristics of Eager Loading in EF Core:
 Single Query: Related data is loaded in the same query as the main entity,
typically resulting in a more complex query with joins.
 Immediate Loading: All specified related data is loaded upfront, regardless
of whether it will be used.
 Controlled Loading: You have explicit control over which related entities are
loaded.
 Performance: This can result in more efficient loading with fewer round trips
to the database, which is especially beneficial for known data requirements.
Lazy Loading in EF Core:
On the other hand, Lazy Loading defers the loading of related data until it is explicitly
accessed for the first time.
Characteristics of Lazy Loading in EF Core:
 Separate Queries: Related data is loaded in separate queries, each
triggered when a navigation property is accessed for the first time.
 Delayed Loading: Data is loaded on-demand, which can save resources if
the related data is never accessed.
 Automatic Loading: EF Core automatically loads the related data, which can
simplify code but may lead to unintended queries.
 Potential for Performance Issues: This can lead to the N+1 query problem,
where multiple queries are executed, potentially impacting performance.
Key Differences Between Eager Loading and Lazy Loading in EF Core:
 Query Strategy: Eager loading retrieves all data in one query, while lazy
loading retrieves data in multiple queries.
 Performance: Eager loading can be more efficient for known data needs,
avoiding multiple round trips. Lazy loading can be more efficient for
conditional data access but may lead to performance issues if not used
carefully.
 Data Retrieval: Eager loading retrieves all related data upfront, while lazy
loading retrieves it as needed.
 Control and Visibility: Eager loading provides more control and visibility
over the data retrieval process, whereas lazy loading abstracts this away.
 Use Cases: Eager loading is suitable when you know you need related data
for each entity. Lazy loading is useful when you need to access related data
conditionally or in a non-predictable manner.
Which is good – Eager Loading or Lazy Loading in Entity Framework
Core?
Before deciding which approach to use, we must consider the application requirements and
goals. Lazy Loading and Eager Loading have strengths and weaknesses, and we have
observed significant performance differences when using each method to accomplish the
same task.
The choice between eager and lazy loading in Entity Framework Core (EF Core) depends
on your specific application requirements, data access patterns, and performance
considerations. Eager loading can be more efficient for complete and predictable data sets,
while lazy loading can benefit dynamic, user-driven data access patterns.
643

Explicit Loading in Entity Framework Core


In this article, I will discuss Explicit Loading in Entity Framework (EF) Core with
Examples. Please read our previous article discussing Lazy Loading in Entity Framework
Core with Examples. At the end of this article, you will understand what is Explicit Loading
and how to implement Explicit Loading in the EF Core. We will work with the same example
we have worked on so far.
Note: If you are a .NET developer, Entity Framework Core (EF Core) could be a great
Object-Relational Mapper (ORM) for you to use. When working with EF Core, loading
related data is a common operation, and there are multiple ways to load related data in EF
Core. Explicit Loading is one example. The other two approaches are Eager
Loading and Lazy Loading.
Explicit Loading in Entity Framework Core
Even if Lazy Loading is disabled in Entity Framework Core, it is still possible to lazily load
the related entities, but it must be done with an explicit call. Here, we need to explicitly call
the Load() method to load the related entities explicitly. That means the EF Core’s Explicit
Loading loads related data from the database separately by calling the Load() method.
Explicit loading in Entity Framework Core is a feature that allows us to load related data of
an entity into the context on demand. This differs from eager loading (using Include) and
lazy loading (where related data is loaded automatically when the navigation property is
accessed). Explicit loading gives you control over when and what related data is retrieved,
which can be useful for optimizing performance and resource utilization.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:22 / 03:1710 Sec

Example to Understand Explicit Loading in Entity Framework Core


Let us understand Explicit loading in Entity Framework Core with an Example. First, let us
disable the Lazy loading for all the entities by setting the LazyLoadingEnabled property to
false within the constructor of the context class. So, first, modify the EFCoreDbContext
class as shown below.
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

{
644

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//Disabling Lazy Loading

this.ChangeTracker.LazyLoadingEnabled = false;

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB1;Trusted_Connection=True;TrustServerCertif
icate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Use this to configure the model using Fluent API

//Adding Domain Classes as DbSet Properties

public virtual DbSet<Course> Courses { get; set; }

public virtual DbSet<Standard> Standards { get; set; }


645

public virtual DbSet<Student> Students { get; set; }

public virtual DbSet<StudentAddress> StudentAddresses { get; set; }

public virtual DbSet<Teacher> Teachers { get; set; }

}
Next, modify the Program class Main method as shown below to ensure Lazy Loading is
Disabled and it is not loading the related data when we explicitly access the navigation
properties.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

var context = new EFCoreDbContext();

//Loading the particular student data only

//Here, it will only load the Student Data, no related entities

Student? student = context.Students.FirstOrDefault(std => std.StudentId == 1);

Console.WriteLine($"Firstname: {student?.FirstName}, Lastname: {student?.LastName}");

Console.WriteLine();

//As Lazy Loading is Disabled so, it will not load the Standard data
646

Standard? standard = student?.Standard;

Console.WriteLine($"StandardName: {standard?.StandardName}, Description:


{standard?.Description}");

Console.WriteLine();

//As Lazy Loading is Disabled so, it will load the StudentAddress data

StudentAddress? studentAddress = student?.StudentAddress;

Console.WriteLine($"AddresLin1 {studentAddress?.Address1}, AddresLin2


{studentAddress?.Address2}");

Console.WriteLine();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

As you can see in the above output, it is not loading the Standard and Address data of the
Student. Now, modify the Main method of the Program class as follows to load the related
Standard and Address data explicitly.
using EFCoreCodeFirstDemo.Entities;
647

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

var context = new EFCoreDbContext();

//Loading the particular student data only

//Here, it will only load the Student Data, no related entities

Student? student = context.Students.FirstOrDefault(std => std.StudentId == 1);

Console.WriteLine($"Firstname: {student?.FirstName}, Lastname: {student?.LastName}");

Console.WriteLine();

//Load Standard Explicit using the Load Method

//If the related Property is a Reference type,

//First, call the Reference method by passing the property name that you want to load

//and then call the Load Method

if( student != null )

//At this point the related Standard Entity is Loaded

context.Entry(student).Reference(s => s.Standard).Load();

//Printing the Standard Data


648

if(student?.Standard != null )

Console.WriteLine("Standard Entity is Loaded");

Console.WriteLine($"StandardName: {student.Standard.StandardName}, Description:


{student.Standard.Description}");

Console.WriteLine();

else

Console.WriteLine("Standard Entity is not Loaded");

Console.WriteLine();

//We have not yet loaded the related StudentAddress Entity

//Printing the StudentAddress Data

if (student?.StudentAddress != null)

Console.WriteLine("StudentAddress Entity is Loaded");

Console.WriteLine($"AddresLin1 {student.StudentAddress.Address1}, AddresLin2


{student.StudentAddress.Address2}");

Console.WriteLine();

else

Console.WriteLine("StudentAddress Entity is not Loaded");


649

Console.WriteLine();

//Loads Courses Collection Explicit using the Load Method

//If the related Property is a Collection type,

//First, call the Collection method by passing the property name that you want to load

//and then call the Load Method

if (student != null)

// At this point the related Courses Entities are Loaded

context.Entry(student).Collection(s => s.Courses).Load();

//Printing the Courses Data

if (student?.Courses != null)

Console.WriteLine("Courses Entities are Loaded");

foreach (var course in student.Courses)

Console.WriteLine($"CourseName: {course.CourseName}");

else

Console.WriteLine("Courses Entities are not Loaded");

Console.WriteLine();
650

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

As you can see in the above output, it is using Explicit Loading to load the related entities.
First, it issues one select statement to fetch the student entity, and then it issues two
separate SQL queries to get the related standard and course data. However, it is not
loading the Student Address, as we are not explicitly loading this entity.
Understanding the Reference, Collection, Entry, and Load Method
651

In the above example, we need to understand two statements,


i.e., context.Entry(student).Reference(s =>
s.Standard).Load(); and context.Entry(student).Collection(s => s.Courses).Load();. So,
let us proceed and understand these two statements in detail.
1. context.Entry(student).Reference(s => s.Standard).Load(): If you want to
load the related entity data, a reference navigation property of the Main entity,
then you need to use this syntax. This statement will generate and execute
the SQL query in the database to fetch the related Standard data and fill the
data with the Standard reference property of the student object.
2. context.Entry(student).Collection(s => s.Courses).Load(): If you want to
load the related entity data, a collection navigation property of the Main entity,
then you need to use this syntax. This statement will generate and execute
the SQL query in the database to fetch the related Courses data and fill the
data with the Courses Collection property of the student object.
You must pass the main entity instance to the Entry method. In this case, the student
instance is our main entity. Then, you must check whether the related entity is a reference
or a collection-type navigation property.
Suppose it is a reference navigation property (in our case, Standard is a reference
navigation property). In that case, we need to call the Reference method, and you need to
specify the Reference Navigation Property name using the string name or the lambda
expression. It is recommended to use a lambda expression. If you misspell the property
name, you will get a compile-time error.
Suppose it is a collection navigation property (in our case, Courses is a collection navigation
property). In that case, we need to use the Collection method, and you need to specify the
Collection Navigation Property name using the string name or the lambda expression.
Finally, you need to call the Load method to generate and execute the SQL Statement in
the database to get the data and fill in the main entity’s specified reference or collection
navigation property.
Advantages and Disadvantages of Explicit Loading in Entity Framework
Core
Explicit loading in Entity Framework Core (EF Core) is a technique that provides developers
with control over when related data should be fetched from the database. While it can be
powerful, it’s essential to understand its pros and cons to use it effectively.
Advantages of Explicit Loading in Entity Framework Core:
 Control: With Explicit Loading in EF Core, we have full control over when to
load the related data from the database. So we can decide when to hit the
database to retrieve the related entities, optimizing performance and memory
usage.
 Efficiency: Useful when you need related data conditionally or in a specific
part of your application, avoiding the upfront cost of loading unnecessary
data.
 Flexibility: You can combine explicit loading with additional LINQ queries to
filter or sort the related data.
 Optimized Data Retrieval: With Explicit Loading in EF Core, we can load data
only when we need the related data, which will reduce unnecessary data fetches
from the database and improve performance, especially in scenarios where the
related data isn’t always required.
652

 Minimized Initial Payload: When retrieving a primary entity, related entities


are not immediately retrieved, making the initial data fetch faster, especially
when dealing with large amounts of data.
Disadvantages of Explicit Loading in Entity Framework Core:
 Complexity: Managing when and what to load can be complex for
developers. They must remember to call explicit loads to avoid null references
or missing data.
 Multiple Database Roundtrips: Each explicit database load results in a
separate database call. This can result in multiple round trips to the database,
which can affect the performance of your application when dealing with a
large number of related entities.
 Avoid N+1 Queries Problem: Avoid the N+1 queries problem, where you
execute one query to fetch the main entities and then N additional queries for
related entities. This can be mitigated by the well-thought-out use of explicit
loading in combination with appropriate query batching.
 Lack of Transparency: It can be confusing and lead to errors for developers
unfamiliar with the codebase to determine when or if related data is loaded.
 Maintenance Overhead: Tracking where explicit loads occur cannot be easy as
the application grows. Developers may need to modify loading strategies as
requirements change.
Explicit Loading vs. Lazy Loading vs. Eager Loading in Entity
Framework Core:
In Entity Framework Core (EF Core), three primary strategies are available for loading
related data: Explicit Loading, Lazy Loading, and Eager Loading. Each approach has its
advantages and disadvantages. Let us proceed and try to understand the differences
between these three approaches and when to use which approach in application
development.
Explicit Loading:
How it works: After fetching the main entity, we need to call the Load method to load
related data.
Advantages:
1. Full control over when to fetch the related data.
2. Ability to conditionally load related data from the database.
Disadvantages:
1. Multiple database roundtrips can affect the performance of your application.
2. Increased complexity: developers must remember to load related data by
explicitly calling the Load method.
Lazy Loading:
How it works: Related data is loaded automatically when we first access the main entity’s
navigation property for the first time.
Advantages:
1. Transparent and automatic; no need to write extra code to load related data.
2. Data is loaded only when needed.
Disadvantages:
1. This can result in the “N+1 Query Problem,” where accessing properties in a
loop can generate many unexpected queries.
653

2. Due to multiple database round trips, this can also lead to performance
issues if not used carefully.
3. Requires additional setup in EF Core, such as installing a separate package
and modifying navigation properties to be virtual.
Eager Loading:
How it works: Related data is loaded upfront using methods like Include and ThenInclude.
Advantages:
1. Data is loaded in a single roundtrip, which can be more efficient in many
scenarios.
2. It indicates which related data will be fetched, providing transparency.
Disadvantages:
1. It might fetch more data than necessary if not used properly.
2. Initial queries can be heavier, fetching all related data at a time, even if some
data isn’t needed immediately.
When to use Which Approach:
Explicit Loading: If you want complete control over when to fetch the related data
depending on conditions or logic in your code, then you should use EF Core Explicit
Loading to load Related Entities.
Lazy Loading: When you want to minimize upfront data retrieval and are okay with fetching
related data on the fly, you can use EF Core Lazy Loading to load Related Entities.
Eager Loading: If you know the related data you need and want to fetch everything at
once, then you need to use EF Core Eager Loading to load Related Entities.
In Real-time Applications, you might use a combination of these strategies based on the
specific needs and performance characteristics of different components of your application.
It is important to understand each approach to make well-informed decisions. It is also
important to keep track of and analyze your database queries to ensure that you achieve
the desired performance and behavior.

Default Conventions in Entity Framework Core (EF Core)


In this article, I will discuss Default Conventions in Entity Framework Core (EF
Core) with Examples. Please read our previous article discussing Explicit Loading in
Entity Framework (EF) Core with Examples.
Default Conventions in EF Core
Entity Framework Core (EF Core) uses a set of default conventions that determines how to
map between our database and entity classes. These conventions help us reduce the
configuration code to achieve this mapping.
In previous articles, we demonstrated examples of using Entity Framework Core. We
observed that the EF Core automatically configured primary keys, foreign keys, indexers,
relationships between tables, and appropriate data types for columns from the domain
classes without requiring extra configurations.
The Entity Framework Core Default Conventions make it possible to automatically configure
the database schema based on conventions set by the EF Core API. Modifying these
default conventions is possible, which we will discuss in our upcoming articles. Let’s
concentrate on understanding the Entity Framework’s Core Default Conventions.
654

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:13 / 03:1710 Sec

Creating the Models:


Let us understand the Default Naming Conventions, followed by the Entity Framework Core.
So, first of all, create the following Entities. So, create a folder with the name Entities and
create the following classes within that folder.
Students.cs
Please create a new class file named Students.cs and copy and paste the following code.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public DateTime? DOB { get; set; }

public byte[]? Photo { get; set; }

public decimal? Height { get; set; }

public float? Weight { get; set; }

public bool IsPassout { get; set; }

public virtual Standard? Standard { get; set; }

public int GradeId { get; set; }

public Grade? Grade { get; set; }

}
Standard.cs
Next, please create another class file named Standard.cs and copy and paste the following
code.
655

namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int Id { get; set; }

public string? StandardName { get; set; }

public string? Description { get; set; }

public virtual IList<Student>? Students { get; set; }

}
Grade.cs
Please create a new class file named Grade.cs and copy and paste the following code.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Grade

public int GradeId { get; set; }

public string? GradeName { get; set; }

public string? Section { get; set; }

public virtual IList<Student>? Students { get; set; }

}
Creating the Context Class:
656

Next, create a class file named EFCoreDbContext.cs and copy and paste the following
code. Here, we have the Student and Standard as the DbSet Properties but we have not
added the Grade Entity as a DbSet Property.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext() : base()

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

//OnModelCreating() method is used to configure the model using ModelBuilder Fluent API

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Use this to configure the model using Fluent API

//Adding Domain Classes as DbSet Properties


657

//In The database it will create the table with the name Students

public DbSet<Student> Students { get; set; }

//In The database it will create the table with the name Standard

public DbSet<Standard> Standard { get; set; }

}
Note: As we already discussed, whenever we add or update domain classes or
configurations, we need to sync the database with the model using add-
migration and update-database commands using Package Manager Console or .NET
Core CLI. Open the Package Manager Console, and then execute the add-migration
CreateEFCoreDB1 command as follows:

Again, in the Package Manager Console execute the Update-Database command as


follows:

Once the Update-Database command is executed successfully, it will update the database
with these two tables.
What are the Default Entity Framework Core Conventions?
Let us understand the Default Entity Framework Core Conventions. They are as follows:
Schema:
By default, the Entity Framework Core API creates all the database objects with the dbo
schema. If you verify the database tables, you will see that the database tables are created
with the dbo schema, as shown in the image below.
658

Table Name:
The EF Core API creates database tables for all properties defined in the context class
as DbSet<TEntity> with the same name as the property name. It can also create tables for
entities not listed as DbSet properties but are still reachable through reference properties in
other DbSet entities. In our example, EF Core will create the Students table for the
DbSet<Student> property, which is there in the EFCoreDbContext class, and
the Grade table for the Grade property, which we defined inside the Student entity class,
despite the EFCoreDbContext class not featuring the DbSet<Grade> property. Similarly, for
the DbSet<Standard> Standard property, EF Core will create the Standard table in the
database. The diagram below may provide a better understanding.

Primary Key Name:


When using EF Core, a primary key column will be automatically generated for a property
named “Id” or “<Entity Class Name> + Id” (regardless of capitalization). For instance, if the
Student class has a property named id, ID, iD, Id, studentid, StudentId, STUDENTID, or
sTUdentID, EF Core will create a Primary Key column in the Students table.
659

In our example, we added StudentId, GradeId, and Id to the respective model classes for
Student, Grade, and Standard. As a result, these three properties will serve as the Primary
Key columns in the corresponding database tables, as illustrated in the image below.

Note: If both Id and <Entity Class Name> + Id properties are present in a single model
class, then it will create the Primary key based on the Id column only. If the model class has
no key property, then Entity Framework will throw an exception.
Foreign Key Column Name:
EF Core API will create a foreign key column for each reference navigation property in an
entity with one of the following naming patterns as per the foreign key convention.
1. <Reference Navigation Property Name>Id
2. <Reference Navigation Property Name><Principal Primary Key Property
Name>
In our example (Student, Grade, and Standard entities), EF Core will create foreign key
columns GradeId and StandardId in the Students table, as shown in the image below.

Here, you can see that in the Student Entity, we have GradeId Property, which will become
the Foreign Key for the Grade table. But we don’t add any Foreign Key Property for
Standard Reference Property. So, in the case of the Standard Reference Property, it will
660

add (Standard + Id) as a Reference Property. This is because the Standard Entity has ID as
the primary key. For a better understanding, please have a look at the Student table.

Null and Not Null Columns:


By default, the Entity Framework Core API will create a null column for all reference type
properties and nullable primitive properties, for example, string, Nullable<int>, Student, and
Standard (all class type properties). The Entity Framework Core will create Not Null
columns for Primary Key properties and non-nullable value type properties, for example, int,
bool, etc. For a better understanding, please look at the image below, which shows the
Student Entity and the corresponding Students database table.

DB Columns Order:
The Entity Framework Core will create the Database table columns in the same order as the
properties are added in the entity class. However, the primary key columns would be moved
to the first position in the table.
Column Data Type
The data type for columns in the database table depends on how the provider for the
database has mapped the C# data type to the data type of a selected database. The
following table lists mapping between the C# data type and the SQL Server column data
type.
661

Index
EF Core creates a clustered index on Primary key columns and a non-clustered index on
Foreign Key columns by default.

Data Annotation Attributes in Entity Framework Core


In this article, I will discuss How to Configure Domain Classes with Data Annotation
Attributes in Entity Framework Core with Examples. Please read our previous article
discussing Default Conventions in Entity Framework Core with Examples.
Data Annotation Attributes in Entity Framework Core
Data Annotations are nothing but Attributes or Classes that can be applied to our domain
classes and their properties to override the default conventions that Entity Framework Core
follows.
662

The Data Annotations Attributes are included in separate namespaces


called System.ComponentModel.DataAnnotations and System.ComponentModel.Data
Annotations.Schema.
Note: Data Annotation Attributes give you only a subset of configuration options in Entity
Framework Core. You need to use Fluent API for the full configuration options.
Ad
1/2
00:17

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV

Data Modelling Attributes in EF Core


Data Modeling Attributes specify the schema of the database. The following Data
Annotation Attributes impact the Schema of a database. These attributes are present in the
namespace System.ComponentModel.DataAnnotations.Schema. The following is the
list of attributes present in the namespace.
1. Table: The Table Attribute can be applied to a domain entity to configure the
database’s corresponding table name and schema. That means it specifies
the database table that a class is mapped to. It has two properties, i.e., Name
and Schema, which specify the corresponding database table name and
schema.
2. Column: The Column Attribute can be applied to a property of a domain
entity to configure the corresponding database column name, order, and
data type. That means it represents the database column that a property is
mapped to. It has three properties, i.e., Name, Order, and TypeName, to
specify the database’s column name, order, and data type.
3. Index: The Index Attribute can be applied to a property of a domain entity to
configure that the corresponding database column should have an Index in
the database.
4. ForeignKey: The ForeignKey Attribute can be applied to a property of a
domain entity to mark it as a foreign key column in the database. That means
it denotes a property used as a foreign key in a relationship.
5. NotMapped: The NotMapped Attribute can be applied to a property or entity
class that should be excluded from the model and not generate a
corresponding column or table in the database. That means that a property or
class should be excluded from database mapping.
6. InverseProperty: The InverseProperty Attribute is applied to a property to
specify the inverse of a navigation property that represents the other end of
the same relationship.
7. ComplexType: This attribute specifies that the class is a complex type.
8. DatabaseGenerated: We use this attribute to specify that the database
automatically updates the value of this property.
Validation Related Attributes
The following Data Annotation Attributes impact the nullability or size of the column in a
database, and these Attributes belong to
the System.ComponentModel.DataAnnotations namespace.
663

1. Key: The Key Attribute can be applied to a property of a domain entity to


specify a key property and make the corresponding database column
a Primary Key column. That means it denotes one or more properties that
uniquely identify an entity. Specifies that a property is the primary key for an
entity.
2. Timestamp: The Timestamp Attribute can be applied to a property of a
domain entity to specify the data type of the corresponding database column
as a row version.
3. ConcurrencyCheck: The ConcurrencyCheck Attribute can be applied to a
property of a domain entity to specify that the corresponding column should
be included in the optimistic concurrency check.
4. Required: The Required Attribute can be applied to a property of a domain
entity to specify that the corresponding database column is a NotNull
column. That means it specifies that a data field value is required. Indicates
that a property is required, resulting in a NOT NULL column in the database.
5. MinLength: The MinLength Attribute can be applied to a property of a
domain entity to specify the minimum string length allowed in the property.
That means it specifies the minimum length of string data allowed in a
property. It will not affect the database column. Specifies the minimum length
of string data allowed in a property.
6. MaxLength: The MaxLength Attribute can be applied to a property of a
domain entity to specify the maximum string length allowed in the
corresponding database column. That means it specifies the maximum length
of string data allowed in a property. It will set the length of the column in the
database. Specifies the maximum length of string data allowed in a property.
7. StringLength: The StringLength Attribute can be applied to a property of a
domain entity to specify the minimum and maximum string length allowed in
the property. That means it specifies the minimum and maximum length of
characters allowed in a data field.
Note: In Entity Framework Core (EF Core), there are two ways to configure domain classes
and database design: Data annotations and the Fluent API. Data annotations are attributes
placed on entity class properties. They provide EF Core with metadata information
regarding the constraints, mappings, and other configurations that should apply to a
database column linked with that property.

Table Attribute in Entity Framework Core


In this article, I will discuss Table Data Annotation Attribute in Entity Framework Core
(EF Core) with Examples. Please read our previous article discussing the basics of Data
Annotation Attributes in Entity Framework Core. The Table Data Annotation Attribute is
applied to an entity to specify the name of the database table that the entity should map to.
Table Data Annotation Attribute in Entity Framework Core
The Table Data Annotation Attribute in Entity Framework Core can be applied to a domain
class to configure the corresponding database table name and schema. It overrides the
default convention in Entity Framework Core. As per the default conventions, Entity
664

Framework Core will create a database table whose name is the same as the DbSet
Property name.
Note: In Entity Framework Core (EF Core), the [Table] Data Annotation Attribute is used to
configure the database table name to which an entity should be mapped. This is particularly
useful if the default table name EF Core generates based on the entity class name doesn’t
meet your requirements or if you’re working on an existing database and must match table
names exactly.
All the Data Annotation Attributes are inherited from the Attribute abstract class. Now, if you
go to the definition of Table Attribute class, then you will see the following.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec

As you can see in the above TableAttribute class, it has one constructor, two properties,
and one private field. The constructor takes one string parameter, which is nothing but the
database table name, which is mandatory. The Schema property is optional, and the use of
the Name and Schema properties are as follows:
1. Name: The name of the table the class is mapped to. It is a read-only
property.
2. Schema: The schema of the table the class is mapped to. This is optional. It
is a read-write property.
Note: Using square bracket [], we need to specify the attributes.
Syntax to use Table Attribute: [Table(string name, Properties:[Schema = string])
Example to use Table Attribute: [Table(“StudentInfo”, Schema=”Admin”)]
Examples to understand Table Data Annotation Attributes in EF Core:
Let us understand the Table Data Annotation Attribute in Entity Framework Core with an
example. Let us modify the Student Entity class as follows. As you can see, we have
specified the table name as StudentInfo. So, when we add the migration and update the
database, it should create a database table with the name StudentInfo in the database,
665

which will map with the following Student Entity. The Table Attribute belongs to the
System.ComponentModel.DataAnnotations.Schema namespace.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("StudentInfo")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
The Table Attribute is applied to the Student Entity class in the above example. So, the
Entity Framework Core will override the default conventions and create the StudentInfo
database table instead of the Students table in the database, which will be mapped with the
above Student Entity class. Next, modify the context class as follows. As you can see, we
only include the Student as a DbSet Property.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
666

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
Note: As we already discussed, whenever we add or update domain classes or
configurations, we need to sync the database with the codebase using add-
migration and update-database commands using Package Manager Console or .NET
Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.

Now, you can also verify the database, and you should see the following.
667

As you can see, it created the database table name that we specified in our Student model
class using the Table Attribute. Further, you can notice it is created with the schema dbo.
Now, if you want to create the table with a different schema, such as Admin, you need to
use the Schema property of the Table Attribute. For a better understanding, please modify
the Student entity as follows.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("StudentInfo", Schema = "Admin")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
So, again, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. So, every time we change our
model, we need to execute the following two commands. I cannot use Mig1, so I am giving
the migration name Mig2.
668

Now, you can verify the database and see the schema as Admin and the table name as
StudentInfo, as shown in the image below.

When to use Table Attribute in EF Core?


 When you are working with an existing database, and need to match table
names exactly.
 When you want to follow a naming convention in your database that’s
different from your entity class naming convention.
 When you need to specify a schema other than the default.
By using the [Table] attribute, we can assign the table name and schema in a declarative
way, which makes the intent within the entity class more clear. However, the Fluent API can
also achieve the same configuration and is more powerful, offering additional configuration
capabilities that data annotations cannot provide.

Column Data Annotation Attribute in Entity Framework Core


In this article, I will discuss Column Data Annotation Attribute in Entity Framework Core
(EF Core) with Examples. Please read our previous article, discussing the Table Data
Annotation Attribute in Entity Framework Core with Examples.
Column Data Annotation Attribute in Entity Framework Core:
669

The Column Data Annotation Attribute can be applied to one or more properties of a domain
entity to configure the corresponding database column name, column order, and column
data type. That means it represents the database column that a property is mapped to.
In Entity Framework Core (EF Core), the [Column] attribute allows developers to provide
additional configuration related to how an entity class property maps to a database column.
With the [Column] attribute, you can specify things like the name of the database column, its
order, and its data type.
The Column Attribute in EF Core overrides the default convention. As per the default
conventions in Entity Framework Core, it creates the database table column with the same
name and in the same order as the property names specified in the model class. Now, if
you go to the definition of Column Attribute, then you will see the following.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec

As you can see in the above image, this class has two constructors. The 0-Argument
constructor will create the database column with the same name as the property name. In
contrast, the other overloaded constructor, which takes the string name as a parameter, will
create the database table column with the name you passed to the constructor. Again, this
Column attribute has three properties. The meaning of the properties is as follows:
1. Name: The name of the column the property is mapped to. This is a read-only
property.
2. Order: The zero-based order of the column the property is mapped to. It
returns the order of the column. This is a read-write property.
3. TypeName: The database provider-specific data type of the column the
property is mapped to. It returns the database provider-specific data type of
the column the property is mapped to. This is a read-write property.
670

Note: The default value for the _order filed is -1 means EF Core will ignore the order.
Syntax to use Column Attribute: [Column (string name, Properties: [Order = int],
[TypeName = string])]
Example to use Column Attribute: [Column(“DOB”, Order = 2, TypeName =
“DateTime2”)]
Examples to understand Column Data Annotation Attribute in EF Core:
Let us understand the Column Data Annotation Attribute in Entity Framework Core with an
example. Let us modify the Student Entity class as follows. As you can see, we have
specified the Column Attribute with the FirstName and LastName properties. With the
FirstName property, we have not provided the string name, i.e., using the 0-Argument
constructor, so in this case, it will create the database table column with the same name as
the Property name. With the LastName property, we have specified the name as LName,
i.e., using the constructor, which takes one string parameter, so in this case, it will create
the database table column with the name LName mapped to the LastName property. The
Column attribute belongs to the System.ComponentModel.DataAnnotations.Schema
namespace.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("StudentInfo", Schema = "Admin")]

public class Student

public int StudentId { get; set; }

[Column]

public string? FirstName { get; set; }

[Column("LName")]

public string? LastName { get; set; }

}
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig3. The name that you are giving it should not be given earlier.
671

With the above changes in place, run the application and verify the database, and you
should see the following. As you can see, the FirstName is created as FirstName, but the
LastName property is created as the LName column in the database, as expected.

Column Data Type in EF Core:


As we see, the Column Attribute class has a property called TypeName, and
that TypeName property is used to get or set the data type of a database column. That
means this property gets or sets the database provider-specific data type of the column the
property is mapped to. For a better understanding, please modify the Student Entity as
follows. Here, we have set the DateOfBirth column name as DOB and Data type
as DateTime2 using TypeName Property.
using System.ComponentModel.DataAnnotations.Schema;
672

namespace EFCoreCodeFirstDemo.Entities

[Table("StudentInfo", Schema = "Admin")]

public class Student

public int StudentId { get; set; }

[Column]

public string? FirstName { get; set; }

[Column("LName")]

public string? LastName { get; set; }

[Column("DOB", TypeName = "DateTime2")]

public DateTime DateOfBirth { get; set; }

}
So, again, open Package Manager Console and Execute the following add-
migration and update-database commands as we modify the Student Entity.

If you verify the database, it should create a column with the name as DOB and the data
type as DateTime2 instead of DateTime, as shown in the image below.
673

Column Order in Entity Framework Core:


Another property called Order is provided by the Column Attribute class, which is used to
set or get the order of the columns. It is 0-based order, i.e., it will start from 0. As per the
default convention, the Primary Key columns will come first, and then the rest of the
columns based on the order we specified in the Column Attribute Order Property. The most
important point you need to remember is that the Order Property of the Column Attribute
must be applied to all the properties of an Entity with a different index, starting from zero.
For a better understanding, please have a look at the following example.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("StudentInfo", Schema = "Admin")]

public class Student

//Primary Key: Order Must be 0

[Column(Order = 0)]

public int StudentId { get; set; }

[Column(Order = 2)]

public string? FirstName { get; set; }


674

[Column("LName", Order = 4)]

public string? LastName { get; set; }

[Column("DOB", Order = 3, TypeName = "DateTime2")]

public DateTime DateOfBirth { get; set; }

[Column(Order = 1)]

public string? Mobile { get; set; }

}
So, again, open Package Manager Console and Execute the following add-
migration and update-database commands as we modify the Student Entity.

Here, you need to observe one thing. We are getting one warning: Column orders are only
used when the table is first created. In our example, we are not creating the tables but
updating the table structure in the database.
Note: It will be ignored when we apply the Order Property of Column Attribute of an existing
Entity.
So, to understand the Order Property of Column Attribute, let’s create a new table. So,
modify the Student Entity Name from Student to CollegeStudent as follows.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("CollegeStudentInfo", Schema = "Admin")]

public class CollegeStudent

{
675

//Primary Key: Order Must be 0

[Column(Order = 0)]

public int CollegeStudentId { get; set; }

[Column(Order = 2)]

public string? FirstName { get; set; }

[Column("LName", Order = 4)]

public string? LastName { get; set; }

[Column("DOB", Order = 3, TypeName = "DateTime2")]

public DateTime DateOfBirth { get; set; }

[Column(Order = 1)]

public string? Mobile { get; set; }

}
Next, modify the Context class as follows.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

}
676

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<CollegeStudent> CollegeStudents { get; set; }

}
Note: Use CollegeStudent instead of Student in other Entities.
With the above changes in place, open Package Manager Console and Execute the
following add-migration and update-database commands as we modify the Student
Entity.

Now, verify the database table column’s order, and it should be in proper order per the
Order parameter shown in the image below.
677

How to fetch the Column Attribute Details in EF Core?


If you remember, the Column Attribute class has three properties, i.e., Name, Order, and
TypeName, and all these three properties have the get accessors. Let us understand how
to use these get accessors to fetch the column details. Fetch all the column attribute details
like Name, Order, and Data Type. To do so, modify the Main Method of the Program class
as follows:
using EFCoreCodeFirstDemo.Entities;

using System.ComponentModel.DataAnnotations.Schema;

using System.Reflection;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Fetch Attribute Details


678

GetAttribute(typeof(CollegeStudent));

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void GetAttribute(Type t)

PropertyInfo[] propertiesInfo = t.GetProperties();

foreach (PropertyInfo propertyInfo in propertiesInfo)

// Get instance of the attribute.

ColumnAttribute? columnAttribute =
(ColumnAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColumnAttribute));

if (columnAttribute == null)

Console.WriteLine("The Attribute was not found.");

else

// Get the Name, Order and DataType values.

Console.WriteLine($"Name: {columnAttribute.Name}, Order: {columnAttribute.Order},


Data Type: {columnAttribute.TypeName}");
679

}
When you run the above code, whatever Name, Order, and Data type you have specified
using the Column Data Annotation Attribute, only those details will be printed on the
Console window, as shown in the image below.

Now, let us fetch single-column information. We have set the Name, Order, and Data Type
of the DateOfBirth property. Let us fetch that property-related database column information.
To do so, modify the Main Method of the Program class as follows:
using EFCoreCodeFirstDemo.Entities;

using System.ComponentModel.DataAnnotations.Schema;

using System.Reflection;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Fetch Attribute Details


680

GetAttribute(typeof(CollegeStudent));

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void GetAttribute(Type t)

//Fetch the DateOfBirth Property details

PropertyInfo propertyInfo = t.GetProperty("DateOfBirth");

// Get instance of the attribute.

ColumnAttribute columnAttribute =
(ColumnAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColumnAttribute));

if (columnAttribute == null)

Console.WriteLine("The DateOfBirth Column Property was not found.");

else

// Get the Name value.

Console.WriteLine($"Name of Column is: {columnAttribute.Name}");

// Get the Order value.

Console.WriteLine($"Order of Column is: {columnAttribute.Order}");


681

// Get the DataType value.

Console.WriteLine($"Data Type of Column is: {columnAttribute.TypeName}");

}
Output:

When to use Column Attribute in EF Core?


 When you want to map a property to a column with a different name.
 When you need to specify a specific database column type.
 When you want to control the order of columns when generating a new
database schema from your entities.
While the [Column] attribute offers a convenient way to specify column-related
configurations, remember that Entity Framework Core also provides a Fluent API with more
detailed configuration capabilities. For very advanced scenarios, or when you want to keep
configuration separate from your domain classes, the Fluent API can be a better choice.

Key Attribute in Entity Framework Core (EF Core)


In this article, I will discuss Key Data Annotation Attributes in Entity Framework Core
(EF Core) with Examples. Please read our previous article, discussing the Column Data
Annotation Attribute in Entity Framework Core with Examples. At the end of this article,
you will understand the following pointers.
1. What is the Primary Key in the Database?
2. What is the Key Attribute in Entity Framework Core?
3. Examples to Understand the Key Attribute in EF Core
4. How to Make Composite Primary Key in Entity Framework Core?
5. When to use Key Attribute in EF Core?
6. Can we Create a Primary Key on String Property using Entity
Framework Core?
What is the Primary Key in the Database?
The Primary Key is the combination of Unique and NOT NULL Constraints. That means it
will neither allow NULL or Duplicate values into a column or columns on which the primary
682

key constraint is applied. Using the primary key, we can enforce entity integrity, i.e., using
the primary key column value, we can uniquely identify a record.
A table should contain only 1 Primary Key, which can be on single or multiple columns, i.e.,
the composite primary key is also possible. A table must have a primary key to identify each
record uniquely. Entity Framework Core will throw an error while creating or updating the
database if we don’t specify the Primary Key Property.
Key Attribute in Entity Framework Core
The Key Data Annotation Attribute in Entity Framework Core can be applied to a property of
an entity to make it the key property, and it will also make that corresponding column the
Primary Key column in the database.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:25 / 03:1710 Sec
As per the default convention, the EF Core will create the primary key column for the
property whose name is Id or <Entity Class Name> + Id (case insensitive). For instance, if
the Student class has a property named id, ID, iD, Id, studentid, StudentId, STUDENTID, or
sTUdentID, EF Core will create a Primary Key column in the Students table.
For example, let us modify the Student.cs class file as follows. Here, we have added the
StudentId property in the Student class. So, this property will be created as a Primary Key
column in the corresponding database table.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
Next, modify the content class, i.e., EFCoreDbContext.cs, as follows. Here, we are adding
the Student entity as a DbSet Property and providing the property name as Students, so the
database will create the table with the name Students.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

{
683

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mg1. The name that you are giving it should not be given earlier.

Now, if you verify the database, you should see the following. As you can see, the StudentId
column is created as the primary key column.
684

Now, what our business requirement is, we do not want to make StudentId or Id the Primary
key column. Instead, we want a different column. Let us say StudentRegdNo as the primary
key in our database. Then, this default convention of EF Core will not work. Let us prove
this. Let us modify the Student entity class as follows. Here, instead of StudentId, we are
using the StudentRegdNo property.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentRegdNo { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration command.
685

As you can see in the above image, executing the add-migration command throws an
exception saying the entity type ‘Student’ requires a primary key to be defined. If you intend
to use a keyless entity type, call ‘HasNoKey’ in ‘OnModelCreating.’
How to Overcome the Above Error?
How to overcome the above error means how can we make the StudentRegdNo property
the key property so that EF Core will make this property the Primary Key column in the
corresponding database table?
We need to use the Key Data Annotation Attribute in EF Core. If you go to the definition of
Key Attribute, then you will see the following. It is a sealed class that has a parameterless
constructor. The Key Attribute denotes one or more properties that uniquely identify an
entity.

So, modify the Student class as follows. As you can see in the below code, we decorate the
StudentRegdNo property with the Key Attribute. The Key Attribute belongs to the
System.ComponentModel.DataAnnotations namespace, so include that namespace as well.
The key Attribute will mark the StudentRegdNo property as the key property, and the entity
framework core will generate the Primary key for the StudentRegdNo property in the
database.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

{
686

[Key]

public int StudentRegdNo { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows.

Now, verify the database, and you should see StudentRegdNo as the Primary Key shown in
the image below.
687

Note: The Key Attribute denotes one or more properties uniquely identifying an entity. The
Key attribute can be applied to a property of any primitive data type except unsigned
integers.
How to Make Composite Primary Key in Entity Framework Core?
If you want to make a composite primary key using Entity Framework Core, it is impossible
to use the Key Attribute. It is possible in Entity Framework but not in Entity Framework
Core.
In Entity Framework, the Key Attribute and the Column Data Annotation Attribute with the
Column Order Property can be applied to multiple entity properties, creating a composite
primary key in the corresponding database table.
In EF Core, we need to use the PrimaryKey Attribute to make a Composite Primary Key.
This PrimaryKey attribute can be used for both keys of a single property and composite
keys of multiple properties. If you go to the definition of PrimaryKey Attribute, then you will
see the following. It is a sealed class having one constructor, which takes two parameters.
The first parameter is the string property name, and the second parameter is the optional
params array. Using this optional params array, we can specify other property names.

Note: You cannot apply this Attribute on an already created database table, and if you try,
you will get the following error. The [PrimaryKey] attribute was introduced in EF Core 7.0.
Use Fluent API in older versions. The Composite primary keys are configured by placing the
[PrimaryKey] attribute on the entity type class.
System.InvalidOperationException: To change the IDENTITY property of a column,
the column needs to be dropped and recreated.
To simplify things, let us first delete the EFCoreDB database from the SQL Server using
SSMS and then delete the Migration folder from our project. Next, modify the Student Entity
Class as follows. As you can see here, we have applied the PrimaryKey Attribute on the
Student Entity, and to this attribute, we are passing the RegdNo and SerialNo properties.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[PrimaryKey(nameof(RegdNo), nameof(SerialNo))]

public class Student

public int RegdNo { get; set; }


688

public int SerialNo { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows.

Now, verify the database, and you should see a composite primary key based on two
columns, as shown in the below image.
689

Note: In Entity Framework Core, the most important point you need to remember is that if
you create the Primary Key on a single column, it will also mark that column as an identity
column, provided the column is of integer type. However, creating a Composite Primary Key
will not mark the primary key columns as identity columns.
Can we Create a Primary Key on String Properties using EF Core?
Yes, we can also create the Primary key on a string property or properties. For a better
understanding, modify the Student entity as follows. As you can see, here we have created
the StudentId property with the string data type, and EF core will mark this property as the
Primary Key in the database. But this will not be marked as an Identity column.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public string StudentId { get; set; }

public string SerialNo { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
We can also create a composite Primary Key using string properties. For a better
understanding, modify the Student entity as follows. As you can see, we have marked
the RegdNo and SerialNo properties as String type, and then we create a composite
primary key based on these two properties, and it will work.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[PrimaryKey(nameof(RegdNo), nameof(SerialNo))]

public class Student

{
690

public string RegdNo { get; set; }

public string SerialNo { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
When to use Key Attribute in EF Core?
In EF Core, entities have a primary key that uniquely identifies each record in a table. The
[Key] attribute indicates which property or properties make up the primary key. By default,
EF Core assumes that the property named Id or <ClassName>Id serves as the primary key,
but the [Key] attribute provides an explicit way to specify this. Primary keys are crucial in
relational databases for identifying records in a table in a unique way. We need to use the
[Key] attribute,
 When EF Core’s convention does not correctly identify the primary key
property.
 When working with existing databases where primary key columns might
have non-standard names.
 When being explicit about your intentions for clarity.
The [Key] attribute provides a declarative way to specify the primary key of an entity directly
within the class. If you need more advanced configurations or prefer to separate
configurations from the domain classes, you can utilize the Fluent API.

ForeignKey Attribute in Entity Framework Core


In this article, I will discuss the ForeignKey Data Annotation Attribute in Entity
Framework Core (EF Core) with Examples. Please read our previous article discussing
the Key Attributes in Entity Framework Core with Examples. Before understanding
ForeignKey Attribute in EF Core, let us first understand what is a Foreign Key in a
database.
What is a Foreign Key Constraint in a Database?
Creating relationships between database tables is one of the important concepts in
databases. This mechanism facilitates linking data stored in various tables, making
retrieving information easier and more efficient.
To connect two database tables or to establish a relationship between two database tables,
a Foreign Key must be specified in one table that refers to a unique column (either a
Primary Key or Unique Key) in the other table. This Foreign Key constraint binds the two
database tables together and verifies the presence of data from one table in the other. A
Foreign Key in one table points to a primary or unique key in another table.
ForeignKey Attribute in Entity Framework Core:
691

In Entity Framework Core (EF Core), the ForeignKey Attribute is used to configure the
relationship between two entities. This attribute overrides the EF Core’s default foreign key
convention, which is followed otherwise. By default, Entity Framework Core searches for a
foreign key property in the Dependent Entity with the same name as the primary key
property in the Principal Entity.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:21 / 03:1710 Sec
In Entity Framework Core (EF Core), the ForeignKey attribute is used to specify which
property serves as the foreign key in a relationship between two entities. This attribute
comes in handy, especially when the convention-based naming isn’t followed, and you want
to make it explicit to EF Core about the relationship.
What is a Dependent Entity and a Principal Entity in Entity Framework
Core?
In the context of Entity Framework Core (EF Core) and relational databases in general, the
terms “Dependent Entity” and “Principal Entity” are often used when discussing
relationships between entities, particularly in the context of foreign key relationships.
 Principal Entity: This is the entity on the “one” side of a relationship. It can
exist independently of the dependent entity. In most scenarios, the principal
entity’s primary key will be used as the foreign key in the dependent entity.
 Dependent Entity: This is the entity on the “many” side or the entity that
holds the foreign key reference. It has a dependency on the principal entity
because it cannot exist without a reference to the principal entity (unless the
foreign key is nullable).
Examples to Understand Default ForeignKey Convention in Entity
Framework Core:
In Entity Framework Core, a Relationship always involves two endpoints. Each endpoint
must provide a navigational property that maps to the other end of the relationship. For a
better understanding, consider Standard Entity, which serves as our Principal Entity,
with StandardId as the Primary Key Property. Note that we have a mandatory collection
navigational property called “students” that is necessary for implementing Foreign Key
Relationships using EF Core.
namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int StandardId { get; set; }

public string? StandardName { get; set; }

public string? Description { get; set; }


692

public ICollection<Student>? Students { get; set; }

}
Next, modify the Student Entity as follows. In our example, the Student Entity is the
Dependent Entity. Note that we have included the Standard Reference Navigational
Property, which is mandatory for establishing the Foreign Key in the Student database
table. Additionally, we have included the StandardId property, which refers to the Primary
Key Property of the Standard table.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

//The Following Property Exists in the Standard Entity

public int StandardId { get; set; }

//To Create a Foreign Key it should have the Standard Navigational Property

public Standard? Standard { get; set; }

}
In this example, we will demonstrate the one-to-many relationship between the Student
and Standard Entities. This relationship is represented by including StandardId in
the Student class, which has a reference property called Standard. Meanwhile,
the Standard entity class includes a collection navigation property called Students.
The StandardId property in the Student Class matches the primary key property of
the Standard class. As a result, the StandardId property in the Student class will
automatically become a Foreign Key Property, and the corresponding column in the
database table will also become a Foreign Key Column.
Note: You must remember that both Entities should and must have Navigational Properties
to implement Foreign Key Relationships. In our example, the Student Entity has the
Standard Reference Navigational Property, and the Standard Entity has the Students
693

collection Navigation Property, which will establish the one-to-many


relationship between these two entities. That means one student has one standard, and
one standard can have multiple students. Next, modify the EFCoreDbContext as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student>Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
Note: Please delete the EFCoreDB database from the SQL Server using SSMS if it already
exists, and then also delete the Migration folder from your project if it exists.
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving DbMig1. The name that you are giving it should not be given earlier.
694

If you verify the database, the Foreign key should have been created in the Student
database, as shown in the image below. Here, the Foreign Key Property is created with the
name StudentId.

Suppose the Foreign Key Property does not exist in the Dependent Entity class. In that
case, Entity Framework will create a Foreign Key column in the Database table with
the Primary Key Property Name of the Principal Entity.
To understand this, modify the Student Entity class as follows. Here, you can see we have
not added the StandardId property. So, in this case, Entity Framework will create the
Foreign Key with the name StandardId. We must have added the Standard reference
navigation Property. Otherwise, the foreign key will not be created.
namespace EFCoreCodeFirstDemo.Entities

{
695

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

//To Create a Foreign Key it should have the Standard Navigational Property

public Standard? Standard { get; set; }

}
With the above changes in place, open Package Manager Console and Execute the
following add-migration and update-database commands as shown below.

If you verify the database, the Foreign key should have been created in the Student
database table with StudentId, as shown in the image below.
696

Entity Framework follows these default conventions to create the Foreign Key column in the
database.
What is ForeignKey Attribute in Entity Framework?
The ForeignKey Attribute in Entity Framework Core configures a foreign key relationship
between the entities. It overrides the default foreign key convention, followed by EF Core. It
allows us to specify the foreign key property in the Dependent Entity whose name does not
match the primary key property of the principal entity.
Now, if you go to the definition of ForeignKey Attribute, you will see the following signature.
As you can see, this class has one constructor that takes the associated navigation
property’s name as an input parameter.

The [ForeignKey(name)] attribute can be applied in three ways:


1. [ForeignKey(NavigationPropertyName)] on the Foreign Key Scalar
Property in the Dependent Entity.
697

2. [ForeignKey(ForeignKeyPropertyName)] on the Related Reference


Navigation Property in the Dependent Entity.
3. [ForeignKey(ForeignKeyPropertyName)] on the Collection Navigation
Property in the Principal Entity.
Note: Before proceeding further, we must identify the Principal and Dependent entities. In
our example, the Standard is the Principal Entity, and the Student is the Dependent Entity;
we will create the Foreign Key in the Dependent Entity.
[ForeignKey] on the Foreign Key Scaler Property in the Dependent Entity
The [ForeignKey] Attribute in Entity Framework Core can be applied to the Foreign Key
Scalar Property in the Dependent Entity. In this case, we must specify the related
Navigation Property name in the ForeignKey. For a better understanding, please modify the
Student Entity as follows. As you can see in the below code, we have applied
the [ForeignKey(“Standard”)] on the StandardReferenceId scaler property, which will be
created as the Foreign Key in the database pointing to the Standard table StandardId
(Primary Key) column.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

[ForeignKey("Standard")]

public int StandardReferenceId { get; set; }

//Related Standard Navigational Property

public Standard? Standard { get; set; }

}
With the above changes in place, open the Package Manager Console and Execute add-
migration and update-database commands as shown below.
698

If you verify the database, the foreign key should have been created in the Student
database table with StandardReferenceId, as shown in the image below.

[ForeignKey] on the Reference Navigation Property in the Dependent


Entity
The [ForeignKey] Attribute in EF Core can also be applied to the Reference Navigation
Property of the Dependent Entity. In this case, we need to specify the dependent entity
foreign key scaler property name in the [ForeignKey] Attribute. For a better understanding,
please modify the Student Entity as follows. As you can see, we have StandardReferenceId
in the [ForeignKey] Attribute, i.e., [ForeignKey(“StandardReferenceId”)], which is applied
to the Standard Navigation Property.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

{
699

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

//Related Foreign Key Property

public int StandardReferenceId { get; set; }

[ForeignKey("StandardReferenceId")]

public Standard? Standard { get; set; }

}
In the above example, the [ForeignKey] attribute is applied on the Standard navigation
property, and the name of the foreign key scaler property StandardReferenceId is specified.
Here, Entity Framework Core will create the foreign key column StandardReferenceId in the
Students table in the database.
With the above changes, open Package Manager Console and Execute add-
migration and update-database commands as shown below.

If you verify the database, the foreign key should have been created in the Student
database table with StandardReferenceId, as shown in the image below.
700

[ForeignKey] on the Collection Navigation Property in the Principal


Entity
The [ForeignKey] Attribute in EF Core can also be applied to the Collection Navigation
Property in the Principal Entity, and the related foreign key scaler property name can be
specified in the Dependent Entity. Let us understand this with an example. First, modify the
Student Entity as follows. Here, you can see we have removed the ForeignKey Attribute.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int StandardReferenceId { get; set; }

public Standard? Standard { get; set; }

}
701

We want the Property StandardReferenceId to be created as a Foreign Key column in the


Students database table, which must be pointed to the StandardId Property of
the Standard entity. To do so, modify the Standard Entity as follows. Here, you can see
the [ForeignKey] attribute is applied on the Students collection navigation property, and
here, we are passing the StandardReferenceId property to the [ForeignKey] attribute,
which will make the StandardReferenceId a Foreign Key Property.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int StandardId { get; set; }

public string? StandardName { get; set; }

public string? Description { get; set; }

[ForeignKey("StandardReferenceId")]

public ICollection<Student>? Students { get; set; }

}
With the above changes, open Package Manager Console and Execute add-
migration and update-database commands as shown below.
702

If you verify the database, the foreign key should have been created in the Student
database table with StandardReferenceId, as shown in the image below.

Note: In this article, we have implemented the One-To-Many relationship using EF Core. In
our upcoming articles, I will discuss One-to-One, One-to-Many, and Many-to-Many
Relationships in detail using Entity Framework Core.
Advantages of ForeignKey Attribute in Entity Framework Core
 Explicitness: Using the ForeignKey attribute, you clearly clarify which
property is being used as a foreign key. This clarity can help other developers
who read the code to understand the relationships without inferring them from
conventions.
 Flexibility in Naming: EF Core’s convention for foreign keys relies on
specific naming patterns. If you have existing database schemas or prefer a
different naming convention, the ForeignKey attribute allows you to map your
entities correctly without adhering to the default convention.
 Composite Foreign Keys: For relationships involving multiple columns
(composite keys), the ForeignKey attribute must define which properties are
part of the foreign key.
 Multiple Relationships: In cases where an entity has multiple relationships
to another entity (e.g., a Person entity having both Mother and Father
navigation properties pointing to another Person), the ForeignKey attribute
can help clarify which foreign key property corresponds to which navigation
property.
Disadvantages of ForeignKey Attribute in Entity Framework Core:
 Difficult to Understand: Using the ForeignKey attribute can make your entity
classes difficult to understand, especially if you have many relationships to
define. This can make the code harder to read and maintain compared to
relying on conventions.
703

 Inconsistencies in the Codebase: If you are working in a team setting, and


some developers use the ForeignKey attribute while others rely on default
conventions, it might lead to inconsistencies in the codebase. This can also
make the code harder to understand and maintain.
 Refactoring Concerns: When renaming properties or changing
relationships, you must update the ForeignKey attribute’s argument
accordingly. It can lead to runtime errors or incorrect database mappings if
you forget.
 Potential for Errors: Manually specifying the foreign key introduces the
possibility of mismatches between the navigation property and the specified
foreign key, leading to unexpected behavior or errors.
When deciding between using an attribute or conventions, it’s important to consider your
scenario and preferences. You should have the clarity and flexibility it provides against the
possibility of errors and additional maintenance. If you choose to use the attribute, it’s
important to maintain consistency throughout the codebase.

Index Attribute in Entity Framework Core


In this article, I will discuss the Index Data Annotation Attribute in Entity Framework
Core (EF Core) with Examples. Please read our previous article discussing ForeignKey
Attribute in Entity Framework Core with Examples. At the end of this article, you will
understand how to create Indexes (Clustered, Non-Clustered, and Unique or Non-Unique
Indexes) using EF Core with Examples.
Index Attribute in Entity Framework Core
Entity Framework Core (EF Core) provides the [Index] attribute to create an index on a
particular column in the database. Creating an index on multiple columns using the Index
Attribute is also possible. Adding the Index Attribute to one or more properties of an Entity
will cause Entity Framework Core to create the corresponding index in the database.
So, in Entity Framework Core, the Index attribute defines indexes on columns in your
database to improve query performance. An index allows the database to efficiently look up
rows based on the values in the indexed column(s), similar to how the index of a book helps
you quickly locate information.
Example to Understand Index Attribute in Entity Framework Core:
If you go to the definition of Index Attribute, you will see the following. As you can see, it is a
sealed class with two constructors and a few properties.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:11 / 03:1710 Sec
704

Note: The most important point you need to remember is that you can apply the Index
Attribute at the class level, and to the constructor, we need to specify the property or
properties on which we want to create indexes.
Let us modify the Student Entity Class to use the Index Attribute. Here, you can see we
have applied the Index Attribute on the Student class, and to the constructor, we are
passing the RegistrationNumber property. In this case, an index will be created with the
default naming convention and has no column order, clustering, or uniqueness specified.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[Index(nameof(RegistrationNumber))]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }

}
705

}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student>Students { get; set; }

}
As we already discussed, whenever we add or update domain classes or configurations, we
need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving EFCoreDBMig1. The name that you are giving it should not be given earlier.
706

By default, it will create the Index with the name IX_{Table name}_{Property Name}. As
we have Applied the Index Attribute on the RegistrationNumber property, EF Core will
create the Index with the name IX_Students_ RegistrationNumber shown in the image
below. As you can see, by default, the Index is created as Non-Unique and Non-
Clustered. Later, I will show you how to create a unique Clustered Index.

You might have one question: we applied the Index Attribute on a Single Property, but
here we can see two indexes. How is that possible? Yes, it is possible. This is because,
by default, one Clustered Index is created when we create the Primary Key in a database.
Now, if you want to give a different name to your Index name rather than the auto-
generated index name, you need to use the other overloaded version of the Constructor,
which takes the name parameter. For a better understanding, modify the Student class as
follows. Here, you can see we are providing the name as Index_RegistrationNumber.
using Microsoft.EntityFrameworkCore;
707

namespace EFCoreCodeFirstDemo.Entities

[Index(nameof(RegistrationNumber), Name = "Index_RegistrationNumber")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }

}
Now, open the Package Manager Console and Execute the add-migration and update-
database commands as follows.

Now, it should create the index with the specified name in the database, as shown in the
below image.
708

Creating Index on Multiple Columns using EF Core:


It is also possible to create an Index on Multiple Columns. For this, we need to specify
property names separated by a comma. Let us understand this with an example. Please
modify the Student class as follows. As you can see here, we have specified the
RegistrationNumber and RollNumber properties in the Index Attribute. The Entity
Framework Core will create one composite index based on the RegistrationNumber and
RollNumber columns.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[Index(nameof(RegistrationNumber), nameof(RollNumber), Name =


"Index_RegistrationNumber_RollNumber")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }


709

public int RegistrationNumber { get; set; }

public int RollNumber { get; set; }

}
Now, open the Package Manager Console and Execute add-migration and update-
database commands as follows.

If you verify the database, it should create the index with the specified name based on the
two columns, as shown in the image below.
710

How to Create Clustered and Unique Indexes Using Entity Framework


Core?
By default, Entity Framework Core creates a Non-Clustered and Non-Unique Index. To
create a Unique Index, you must use the following IsUnique properties and set the values to
True.
1. IsUnique: Set this property to true to define a unique index. Set this property
to false to define a non-unique index.
Note: It is impossible to manually create the Clustered Index using the Index
Attribute in EF Core. A table can have a maximum of 1 clustered index, which will be
created on the primary key column by default, and we cannot change this default behavior.
Now, modify the Student class as shown below to create a unique index on the
RegistrationNumber property.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[Index(nameof(RegistrationNumber), Name = "Index_RegistrationNumber", IsUnique =


true)]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }

}
With the above changes, open the Package Manager Console and Execute add-
migration and update-database commands as follows.
711

Now, if you verify the database, then it should create a unique index with the specified
name, as shown in the below image.

Index Sort Order:


In most databases, each column covered by an index can be either ascending or
descending. For indexes covering only one column, this typically does not matter: the
database can traverse the index in reverse order as needed. However, the ordering can be
crucial for composite indexes for good performance. By default, the index sorting order is
ascending. You can make all columns in descending order as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

{
712

[Index(nameof(RegistrationNumber), nameof(RollNumber), AllDescending = true, Name =


"Index_RegistrationNumber_RollNumber")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }

public int RollNumber { get; set; }

}
You may also specify the sort order on a column-by-column basis as follows.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[Index(nameof(RegistrationNumber), nameof(RollNumber), IsDescending = new[] { false,


true }, Name = "Index_RegistrationNumber_RollNumber")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }

public int RollNumber { get; set; }

}
713

}
Can we Create Multiple Indexes on a Table using EF Core?
Yes. It is possible to create multiple indexes on a table using EF Core. To achieve this, we
must decorate the Entity with Multiple Index Attribute. For a better understanding, please
modify the Student Entity as follows. Here, you can see we are creating two composite
indexes. One index on the FirstName and LastName column with the name
Index_FirstName_LastName. The other index is on the RegistrationNumber and
RollNumber columns with the name Index_RegistrationNumber_RollNumber.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

[Index(nameof(FirstName), nameof(LastName), Name = "Index_FirstName_LastName")]

[Index(nameof(RegistrationNumber), nameof(RollNumber), Name =


"Index_RegistrationNumber_RollNumber")]

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }

public int RollNumber { get; set; }

}
Advantages and Disadvantages of using Indexes:
Advantages of using Indexes:
 Performance: Properly indexed columns can dramatically speed up data
retrieval times, making queries more efficient.
 Uniqueness: By setting an index as unique, you ensure that no two rows
have the same value in the indexed column(s).
Disadvantages of using Indexes:
714

 Insert/Update Overhead: While indexes speed up data retrieval, they can


slow down insert and update operations since the index needs to be updated
whenever data changes.
 Storage: Indexes consume additional disk space.
 Over-indexing: It’s possible to have too many indexes. Over-indexing can
lead to increased resource consumption and hurt performance more than it
helps.
When deciding to add an index, consider the specific needs of your application. Typically,
frequently searched or filtered columns are good candidates for indexing.

InverseProperty Attribute in Entity Framework Core


In this article, I will discuss the InverseProperty Data Annotation Attribute in Entity
Framework Core (EF Core) with Examples. Please read our previous article
discussing Index Attributes in Entity Framework Core with Examples.
InverseProperty Attribute in Entity Framework Core
In Entity Framework Core (EF Core), the InverseProperty attribute is used to specify the
inverse navigation property of a relationship when the conventional naming and structure
don’t provide enough information for EF Core to determine which properties are paired in a
navigation relationship correctly.
This attribute is especially useful in situations where:
 There are multiple relationships (multiple navigation properties) between two
entities.
 The property names do not follow conventions that allow EF Core to
determine the relationships automatically.
Example to Understand InverseProperty Attribute in Entity Framework
Core
The InverseProperty Attribute in Entity Framework Core is used when two entities have
more than one relationship. A relationship in EF Core has two endpoints. Each end must
return a navigational property that maps to the other end of the relationship.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec
The InverseProperty Attribute informs the EF Core, which navigational property relates to
the other end of the relationship. The default convention in Entity Framework Core correctly
identifies only if a single relationship exists between two entities. However, in the case of
multiple relationships between two entities, it fails to identify them correctly. In such cases,
we must use the InverseProperty Attribute to help the EF Core correctly identify the
relationship. If this is unclear now, don’t worry; we will try to understand this with an
example.
Let us understand InverseProperty Attribute in EF Core with one example. To understand
this concept, we will create two Entities, i.e., Course and Teacher. In our example, the
Teacher Entity will be the Principal Entity, and the Course Entity will be the Dependent
Entity. We will create the Foreign Keys inside the Dependent Entity to establish the
relationships.
715

Create a class file named Course.cs, then copy and paste the following code. Here, we
have created the following class with CourseId, CourseName, and Description properties
along with the Teacher Reference Navigation Property, which makes the relationship One-
To-One between the Course and Teacher.
namespace EFCoreCodeFirstDemo.Entities

public class Course

public int CourseId { get; set; }

public string? CourseName { get; set; }

public string? Description { get; set; }

public Teacher? OnlineTeacher { get; set; }

}
Create a class file named Teacher.cs and copy and paste the following code. Here, we
have created the following class with TeacherId and Name properties and the Course
Collection Navigation Property, which makes the relationship One-to-Many between the
Teacher and Course.
namespace EFCoreCodeFirstDemo.Entities

public class Teacher

public int TeacherId { get; set; }

public string? Name { get; set; }

public ICollection<Course>? OnlineCourses { get; set; }

}
716

The two entities have a one-to-many relationship, which is possible because of the
Navigation Property, as shown in the image below. So, each entity must have a navigation
property that maps to the navigation property of another entity.

Next, modify the context class as follows:


using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Course> Courses { get; set; }

public DbSet<Teacher> Teachers { get; set; }

}
717

As we already discussed, whenever we add or update domain classes or configurations, we


need to sync the database with the model using add-migration and update-
database commands using Package Manager Console or .NET Core CLI.
So, open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving DBMigration1. The name that you are giving it should not be given earlier.

In our example, the Course and Teacher entities have a one-to-many relationship where
one teacher can teach many online courses, and a single teacher can teach one course. As
per the default conventions of Entity Framework Core, the above example would create the
following tables in the database.
718

Now, suppose our requirement is to add another one-to-many relationship between the
Teacher and Course Entities. For example, apart from OnlineTeacher, we now need to add
OfflineTeacher. First, modify the Teacher class to include the OfflineCourses collection
Navigation Property.
namespace EFCoreCodeFirstDemo.Entities

public class Teacher

{
719

public int TeacherId { get; set; }

public string? Name { get; set; }

public ICollection<Course>? OnlineCourses { get; set; }

public ICollection<Course>? OfflineCourses { get; set; }

}
Next, modify the Course Entity as follows to include the OfflineTeacher Reference
Navigational property.
namespace EFCoreCodeFirstDemo.Entities

public class Course

public int CourseId { get; set; }

public string? CourseName { get; set; }

public string? Description { get; set; }

public Teacher? OnlineTeacher { get; set; }

public Teacher? OfflineTeacher { get; set; }

}
Now, the Course and Teacher entities have two one-to-many relationships. An online
teacher or an offline teacher can teach a Course. In the same way, a teacher can teach
multiple online courses as well as multiple offline courses.
So, if we have only one relationship between two entities, it works fine. But when we have
more than one relationship between two entities, EF Core throws an exception while
executing the add-migration.
With the above changes, open the Package Manager Console and Execute the add-
migration command. And you should get the following error. It is clearly saying that it is
unable to determine the relationship.
720

How to Overcome this Problem in EF Core?


To overcome this problem, we must use the InverseProperty Attribute in EF Core. If you
go to the definition of InverseProperty class, you will see the following. As you can see, the
InverseProperty class has one constructor, which takes a string property parameter and a
read-only property to return the property name. This class specifies the inverse of a
navigation property that represents the other end of the same relationship.

Let us modify the Teacher Entity class as follows to use the InverseProperty Attribute. As
you can see in the code below, we have decorated the InverseProperty Attribute with
the OnlineCourses and OfflineCourses property and also specified the Course Entity
Navigation property, which should point to make the relationship. With
this, OnlineCourses will have a relationship with the OnlineTeacher property,
and OfflineCourses will have a relationship with the OfflineTeacher property.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Teacher

public int TeacherId { get; set; }

public string? Name { get; set; }


721

[InverseProperty("OnlineTeacher")]

public ICollection<Course>? OnlineCourses { get; set; }

[InverseProperty("OfflineTeacher")]

public ICollection<Course>? OfflineCourses { get; set; }

}
As you can see, we have applied the [InverseProperty] attribute on two collection
navigation properties, i.e., OnlineCourses and OfflineCourses, to specify their related
navigation property in the Course entity. For a better understanding, please have a look at
the below image.

With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving DBMigration2. The name that you are giving it should not be
given earlier.

Now, verify the database, and you should see the following. As you can see, in this case,
Entity Framework Core creates foreign keys OnlineTeacherTeacherId and
OfflineTeacherTeacherId.
722

Advantages and Disadvantages of InverseProperty Attribute in EF Core:


Advantages:
 Clarity: Provides an explicit way to denote the relationships between
navigation properties, making the model clearer.
 Flexibility: Allows for deviation from naming conventions without losing the
intended relationships.
Disadvantages:
 Verbosity: It can add additional annotations to the model, making it slightly
more verbose.
 Maintenance: When refactoring or renaming properties, developers need to
remember to update the InverseProperty attribute values accordingly.
So, the InverseProperty attribute is a useful tool for explicitly specifying relationships in EF
Core when conventions are insufficient or ambiguous. It helps ensure that the framework
correctly interprets the relationships.
723

NotMapped Attribute in Entity Framework Core (EF Core)


In this article, I will discuss NotMapped Data Annotation Attribute in Entity Framework
Core (EF Core) with Examples. Please read our previous article,
discussing InverseProperty Attribute in Entity Framework Core with Examples.
NotMapped Attribute in Entity Framework Core:
The NotMapped Attribute in Entity Framework Core can be applied to one or more
properties of an entity class for which we do not want to create corresponding columns in
the database table. By default, EF Core creates a column for each property (property
having the get and set accessors) in an entity class.
Using NotMapped Attribute, we can also exclude a class from database mapping, i.e., if we
apply this attribute on an Entity, for this Entity, no database table is going to be created in
the database. So, the [NotMapped] Attribute overrides the default convention that EF Core
follows.
Examples to Understand NotMapped Attribute in EF Core:
Before using the NotMapped Attribute in Entity Framework Core, let us first see the
signature of this Attribute class. If you go to the definition of NotMappedAttribute class, you
will see the following. The NotMapped Attribute denotes that property or class should be
excluded from database mapping. As you can see, this class has one parameterless
constructor.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:08 / 03:1710 Sec

Let us understand the NotMapped Attribute with an Example.


Create a class file named StudentOnline.cs class and copy and paste the following code.
As you can see, we created the class with a set of properties here. Then, we
applied NotMapped Attribute with RegistrationNumber Property, which should exclude the
corresponding column while creating the StudentOnline database table.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class StudentOnline

{
724

public int StudentOnlineId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

//Applying NotMapped Attribute in Properties

//For the following Property, no database column will be created

[NotMapped]

public int RegistrationNumber { get; set; }

}
Note: If you have a property in your entity that you do not want to be represented as a
column in the database, you can decorate it with the NotMapped attribute.
Please create another class file named StudentOffline.cs class and copy and paste the
following code. As you can see, we also created the class with the same set of properties
here. Then, we applied the NotMapped attribute on the StudentOffline Entity class, which
should exclude the corresponding database table to be created in the database.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

//Applying NotMapped Attribute in Entity Class

//For the following Entity, no database table will be created

[NotMapped]

public class StudentOffline

public int StudentOfflineId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public int RegistrationNumber { get; set; }


725

}
Note: If you have a class that you use in your DbContext but don’t want a corresponding
table in the database, you can decorate the whole class with the NotMapped attribute.
Next, modify the context class as follows. As you can see, we have registered the two
model classes within the context class using DbSet properties.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<StudentOnline> OnlineStudents { get; set; }

public DbSet<StudentOffline> OfflineStudents { get; set; }

}
Note: Before proceeding further, I am deleting the Migration Folder from our project and the
database.
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration1. The name that you are giving it should not be given earlier.
726

Now, if you verify the database, you will only see the StudentOnlines table, and you can
also verify that there is no column mapping for the RegistrationNumber property, as
shown in the image below.

Note: Even though we have specified the StudentOffline entity in the context class, the
corresponding database table is not generated. This is because we have applied the
StudentOffline entity with the NotMapped Attribute.
The Entity Framework Core will not create a column in the database table for a property if it
does not have both getters or setters. To better understand, please modify
the StudentOnline.cs class as follows. As you can see, for the
Property RegistrationNumber, we only GET Accessor, not the SET Accessor. And for the
Property RollNumber, we have only the SET Accessor, not the GET Accessor. So, in this
case, for RollNumber and RegistrationNumber Properties, the corresponding database
columns will not be created in the database.
namespace EFCoreCodeFirstDemo.Entities

{
727

public class StudentOnline

private int _RollNumber;

//Properties Having both get and set Accessor

public int StudentOnlineId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

//Property Having only get Accessor

public int RegistrationNumber { get { return StudentOnlineId; } }

//Property Having only set Accessor

public int RollNumber { set { _RollNumber = value; } }

}
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration2. The name that you are giving it should not be given earlier.

If you verify the database, you will see no column mapping for the RegistrationNumber and
RollNumber properties, as shown in the image below.
728

Advantages and Disadvantages of NotMapped Attribute in EF Core:


Advantages of NotMapped Attribute in EF Core:
 Flexibility: Allows you to have properties in your entities that exist only in
your application logic and aren’t persisted in the database.
 Performance: Excluding unnecessary columns can simplify the database
schema and optimize the performance of CRUD operations by not dealing
with fields that don’t need to be stored or retrieved.
 Application Logic vs. Data Logic: Not all properties in an entity need to be
persisted in the database. Some properties might exist purely for the
application’s logic. The NotMapped attribute allows developers to distinguish
between application-specific logic and data that should be persisted.
 Avoid Redundancy: If you have properties that can be derived or computed
from other properties, marking them as NotMapped prevents redundant
storage. This is common with calculated properties.
Disadvantages of NotMapped Attribute in EF Core:
 Confusion: If not clearly documented or understood, other developers might
be unaware that a certain property isn’t persisted in the database. They might
assume that all properties of an entity are saved, which could lead to data
loss or other unexpected behaviors.
 Maintenance: If the requirements change and a previously non-persistent
property now needs to be stored in the database, developers need to remove
the NotMapped attribute, ensure that migrations are correctly applied, and
handle potential data initialization or transfers.
 Consistency: In a large codebase, if some developers use the NotMapped
attribute while others use the Fluent API configuration to achieve the same
goal, it might lead to inconsistencies in how entities are defined.
 Possible Overuse: Developers might misuse the NotMapped attribute to
exclude properties they’re unsure about rather than making definitive
modeling decisions, leading to a domain model that’s not well-defined or
thought out.
729

When using Entity Framework Core (EF Core), the NotMapped attribute specifies that a
particular property or class should not be mapped to a table or column in the database. EF
Core usually maps all entity properties to database columns, but in some cases, certain
properties should not be persisted in the database. In these situations, the NotMapped
attribute is used to exclude those properties from being persisted in the database.

Required Attribute in Entity Framework Core


In this article, I will discuss the Required Data Annotation Attribute in Entity Framework
Core (EF Core) with Examples. Please read our previous article discussing NotMapped
Attribute in Entity Framework Core with Examples.
Required Attribute in Entity Framework Core
The Required Data Annotation Attribute in Entity Framework Core can be applied to one or
more properties of an entity class. If we apply the Required Attribute to a Property, then
Entity Framework will create a NOT NULL column for that Property in the database. NOT
NULL Column means it will not accept NULL Value in that column in the database.
You will see the following signature if you go to the Required Data Annotation Attribute
definition. The Required Attribute belongs to
the System.ComponentModel.DataAnnotations namespace.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:07 / 03:1710 Sec
The above Required Attribute class specifies that a data field value is required. As you can
see, this class has one parameterless constructor, one property, i.e., AllowEmptyStrings,
and one overridden method, i.e., IsValid.
Note: When using Entity Framework Core (EF Core), the Required Data Annotation
Attribute is used to specify that a property on an entity must have a value and cannot be
null. This attribute is applied to both the database schema (by creating a non-nullable
column) and used for model validation when working with ASP.NET Core MVC and Web
API Applications.
Examples to Understand Required Attribute in Entity Framework Core:
730

By default, for Nullable .NET Data types such as String, EF Core creates the column as a
NULL column, which can accept NULL Values. Let us first understand this default Entity
Framework Core Conventions, and then we will see how to use the Required Data
Annotation Attribute. First, modify the Student.cs class file as follows.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? Name { get; set; }

public string? Address { get; set; }

public int RollNumber { get; set; }

}
As you can see, here we have created the Student Entity class with four properties. Two
Integer properties and two string properties. As the integers are primitive types, they cannot
hold null values by default. so, for the integer properties, the Entity Framework Core will
create the database column with NOT NULL Constraint, which cannot store a null value.
The string is a reference type, and as they hold a null value by default, for string type
properties, the EF Core will create a NULL type column in the database that can store a
NULL value.
Next, modify the context class as follows. As you can see, we have registered the Student
model class within the context class using the DbSet property.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

{
731

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration101. The name that you are giving it should not be given
earlier.

If you verify the database, you will see NOT NULL columns for integer properties. You will
see NULL columns for string properties, as shown in the image below.
732

How to make the Name Column a NOT NULL Column?


Now, what is our requirement? we need to accept a NULL value for the Address column,
but we do not want to accept a NULL Value for the Name column. To do so, we must
decorate the Required Data Annotation Attribute with the Name Property of our Student
Entity Class. So, modify the Student.cs class file as follows.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

[Required]

public string? Name { get; set; }

public string? Address { get; set; }

public int RollNumber { get; set; }

}
733

}
With the changes in place, open Package Manager Console and Execute the following add-
migration and update-database commands. You can give any name to your migration.
Here, I am giving DBMigration101. The name that you are giving it should not be given
earlier.

If you check the Students database table, you will see that the Name database column is
created using the NOT NULL constraint, as shown in the image below.

Uses:
 Database Level: When migrations are created and applied, EF Core will
generate an SQL schema where the column corresponding to the Name
property is non-nullable.
734

 Runtime Validation: If you attempt to save an entity that violates any of the
Required constraints (for instance, if you try to save a Student entity without a
Name), EF Core will throw a DbUpdateException.
Example:
Now, to see whether it is working as expected or not, please modify the Main method of the
Program class as shown below. As you can see, we are not providing any value for the
Name property, and hence, it will take the default value null, and when we call the
SaveChanges method, it will throw a runtime exception.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

Student std = new Student()

Address = "Test",

RollNumber = 1

};

using var context = new EFCoreDbContext();

context.Add(std);

context.SaveChanges();

Console.WriteLine("Student added Successfully");

catch (Exception ex)


735

Console.WriteLine($"Error: {ex.Message}"); ;

}
How to Restrict Empty Strings in NOT NULL Properties?
Now, our requirement is we will not allow a NULL value, but we want to allow an Empty
value. It would be best to remember that NULL and Empty are different. They are different.
The value null represents the absence of any object, while the empty string is an object of
type String with zero characters.
To accept an empty string in a NOT NULL String Column, we need to use
the AllowEmptyStrings property and set its value to true. This property of
the Required Attribute class gets or sets a value indicating whether an empty string is
allowed. You need to set the value to true if an empty string is allowed; otherwise, false.
The default value is false.
Let us modify the Student Entity class as follows to use Required Attribute with
AllowEmptyStrings property and let us set its value to true.
using Microsoft.EntityFrameworkCore;

using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

[Required(AllowEmptyStrings = true)]

public string? Name { get; set; }

public string? Address { get; set; }

public int RollNumber { get; set; }

}
736

}
Advantages and Disadvantages of Required Attribute in Entity
Framework Core
Advantages of Required Attribute in Entity Framework Core:
 Data Integrity: By marking a property as Required, we enforce that specific
pieces of data are present, maintaining the integrity and consistency of the
data in your database.
 Clear Model Definitions: By marking properties as Required, developers
can immediately understand which fields are mandatory, leading to clearer
and more understandable entity models.
 Automatic Validation: EF Core will automatically perform validation checks
against entities that use the Required attribute. EF Core will ensure that
required fields are populated before changes are saved to the database,
reducing the risk of erroneous data entering the database.
 Database Schema Enforcement: The Required attribute Apply the NOT
NULL constraint to the Column in the database, ensuring the rule is enforced
at both the application and database levels.
Disadvantages of Required Attribute in Entity Framework Core:
 Initial Migration Challenges: If you add the Required attribute to an existing
property and the database already contains data, you might face challenges
when applying the migration. Existing null values in the database will conflict
with the new non-nullable constraint. Handling this requires additional work,
such as providing default values or cleaning up existing data.
 Overhead: Validation checks introduce a slight overhead. In most cases, this
is negligible, but it’s something to consider if you’re doing bulk inserts or
updates.
 Flexibility: There might be scenarios where a property might change based
on evolving business rules or application logic. In such cases, using the
Required attribute can be restrictive.

MaxLength and MinLength Attribute in Entity Framework Core


In this article, I will discuss MaxLength and MinLength Data Annotation Attributes in
Entity Framework Core (EF Core) with Examples. Please read our previous article
discussing Required Attribute in Entity Framework Core with Examples.
MaxLength and MinLength Attribute in Entity Framework Core
The MaxLength Data Annotation Attribute in Entity Framework Core specifies the maximum
data length allowed for a property. This MaxLength Attribute will set the corresponding table
column size in the database. In simple words, we can say that using MaxLength Data
Annotation Attribute, we can set the size of the database column. If we enter a value greater
than the specified size, it will throw an exception.
The MinLength Data Annotation Attribute in Entity Framework Core specifies the minimum
data length allowed for a property. In this case, it will set the size of the corresponding
database table column as max. In simple words, we can say that using MinLength Data
Annotation Attribute, we can validate the data length we will store in the property. If we
737

enter a value less than the specified size, it will throw an exception. It is a validation
attribute that will not change the database schema.
By default, for string or byte[] properties of an entity, Entity Framework will set the size of
the database column as max. For string properties, it will create the column as
nvarchar(max), and for byte[] properties, it will create the column as varbinary(max).

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec
MaxLength Attribute:
 Limits the number of characters allowed in a string field.
 It can also be applied to byte array properties, limiting the number of bytes in
the field.
 When used with string properties, it affects the underlying database schema,
setting the maximum length for the corresponding column.
MinLength Attribute:
 Specifies the minimum length of the string.
 Unlike MaxLength, MinLength does not affect the database schema. It’s
purely a validation constraint and will be checked when you attempt to save
the entity.
Example to Understand MaxLength and MinLength Attribute in EF Core
Let us understand the default convention with an example, and then we will see how to use
MinLength and MaxLength Data Annotation Attributes in EF Core. Please modify the
Student Entity as follows. Here, we have created the Student Entity with four properties.
One Integer Property, two string properties, and one byte[] property. In this case, for string
properties, it will set the corresponding database column as nvarchar(max). For the byte[]
property, it will set the corresponding database column as varbinary(max).
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

public byte[]? Photo { get; set; }

}
738

Next, modify the context class as shown below. As you can see, we have registered the
Student model within the context class using DbSet.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving DBMig1. The name that you are giving it should not be given
earlier.
739

Now, if you check the Students database table, you will see it created the corresponding
database columns with the maximum size shown in the image below.

How to Set the Max Length and Min Length in EF Core?


Now, we need to provide some restrictions on the data that we are going to store in the
database. Our requirement is the maximum length for the First Name value is 50
Characters. The Minimum Length for the Last Name value is 5 Characters. We need to use
the MaxLength(50) and MinLength(5) Attributes for this.
So, modify the Student Entity Class as follows. We have applied the MaxLength(50) Data
Annotation Attribute on the FirstName Property, which will also set the corresponding
database column length as 50. If we enter the FirstName value of more than 50 characters,
it will throw an exception. Then, we applied the MinLength(5) Data Annotation Attribute with
the LastName Property, which will throw an exception if we enter a value of less than 5
characters. For MinLength Attribute, Entity Framework will set the corresponding database
column length as max.
740

using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

[MaxLength(50)]

public string? FirstName { get; set; }

[MinLength(5)]

public string? LastName { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving DBMig2. The name that you are giving it should not be given
earlier.

If you verify the database, you will see that the FirstName column will be created with size
50, and the LastName column will be created with the size max, as shown in the image
below.
741

Note: You must remember that Entity Framework validates the MaxLength and MinLength
property values. For MaxLength, whatever value you specified will be set as the
corresponding database column size. For MinLength, Entity Framework will set the
corresponding database column size as max. The MinLength validation will come into the
picture when we work with the ASP.NET Core MVC and ASP.NET Core Web API
Applications.
How can we set both MaxLength and MinLength Attribute in a Single
Property?
Applying both MaxLength and MinLength Attribute in a single Property is also possible. For
example, our requirement is to set the Maximum Length of the Student’s first name as 10
Characters and the Minimum Length for the Student’s first name as 5 Characters. Then, we
need to use both MaxLength and MinLength Attribute in the FirstName property of the
Student Entity as follows.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

[MaxLength(10), MinLength(5)]

public string? FirstName { get; set; }

public string? LastName { get; set; }


742

StringLength Attribute: In ASP.NET Core, the StringLength attribute is used for model
validation. It provides a way to set both minimum and maximum length constraints on a
string property. This is particularly useful for input validation when handling form
submissions.
Advantages and DisAdvantages of MaxLength and MinLength Attribute
in Entity Framework Core
Using the MaxLength and MinLength attributes in Entity Framework Core provides a way to
impose constraints on the data and maintain data integrity. Let’s understand their
advantages and disadvantages:
Advantages:
 Data Integrity: MaxLength and MinLength ensure that the data adheres to
the set length constraints, preventing errors or inconsistencies in the data.
 Database Optimization: MaxLength helps optimize storage, as setting
appropriate column sizes can save space in the database. For example, a
VARCHAR(50) typically uses less storage than a VARCHAR(MAX).
 Automatic Schema Generation: When using Code First migrations in EF
Core, MaxLength will influence the database schema, setting the size of
string columns automatically based on the annotation.
 Consistent Validation: Setting these constraints at the model level ensures
consistent validation throughout the application. The validation logic isn’t
scattered or duplicated.
 Expressive Code: Annotations make the model self-documenting.
Developers can easily discern the intended length constraints by looking at
the model class.
Disadvantages:
 No Database Impact with MinLength: The MinLength attribute is purely for
validation during runtime in EF Core. It doesn’t affect the database schema,
meaning you rely on application logic rather than database constraints to
ensure data integrity for minimum lengths.
 Overhead: Applying constraints introduces overhead when saving data, as
EF Core needs to validate entities against these constraints during
SaveChanges().
 Less Flexibility for Schema Evolution: If you set a MaxLength and later
decide to need a larger column size, you’ll have to modify the model and then
create and apply a migration to change the database schema.
 Error Handling: You must handle validation exceptions arising from violating
these constraints. If not handled appropriately, it might result in poor user
experiences.
 Coupling: Data annotations couple the domain model to the persistence
layer. This might not be ideal for architectures aiming for a strict separation
between domain logic and data persistence.
743

DatabaseGenerated Attribute in Entity Framework Core


In this article, I will discuss DatabaseGenerated Data Annotation Attribute in Entity
Framework Core (EF Core) with Examples. Please read our previous article,
discussing MaxLength and MinLength Attribute in Entity Framework Core with
Examples. In Entity Framework Core, the DatabaseGenerated attribute indicates how the
database generates or manages a property’s value when performing insert or update
operations. This attribute can be applied to properties of our entity classes to control how
their values are handled.
DatabaseGenerated Attribute in EF Core
In Entity Framework Core (EF Core), the DatabaseGenerated attribute is used to specify
how values are generated by the database for a particular property in an entity class. This is
particularly useful for handling properties whose values are computed or set by the
database, such as primary keys, timestamps, or computed columns.
As we already discussed, by default, Entity Framework Core creates an IDENTITY column
in the database for the Key Property of the entity (It will not generate an identity column for
the composite key or composite primary key). In the case of Primary Key with a single
column, SQL Server creates an integer IDENTITY column with identity seed and increment
values as 1. So, while inserting a record into the database table, the identity column value
will be automatically generated by SQL Server and inserted into the identity column or
primary key column.
EF Core provides the DatabaseGenerated Data Annotation Attribute, configuring how a
property’s value will be generated. The DatabaseGenerated Attribute belongs to the
System.ComponentModel.DataAnnotations.Schema namespace. If you go to the definition
of DatabaseGenerated Attribute class, you will see the following signature. This class has
one constructor and one property.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:23 / 03:1710 Sec

As you can see in the above image, the constructor takes one parameter of type
DatabaseGeneratedOption. That means the DatabaseGenerated attribute accepts a
DatabaseGeneratedOption enum as its parameter. Now, if you go to the definition of
DatabaseGeneratedOption, you will see that it is an enum with three values, as shown in
the image below.
744

There are different values you can set for the DatabaseGenerated Attribute in EF Core:
1. DatabaseGeneratedOption.None: This is the default option and indicates
that the database does not generate the property value. It’s typically used for
properties you want to set in your application manually. Use this option if you
do not want the database to generate a value.
2. DatabaseGeneratedOption.Identity: This option indicates that the property
is an identity column in the database, and the database automatically
generates its value during an insert operation. This is often used for primary
key columns. If you want the database to generate a value for a property
when a row is inserted, then you need to use this option.
3. DatabaseGeneratedOption.Computed: This option indicates that the
database computes the property value during insert or update operations.
This can be used for properties whose values are based on calculations or
expressions involving other columns. If you want the database to generate a
value for a property when a row is inserted or updated, then you need to use
this option.
Let us understand each of the above options with examples.
DatabaseGeneratedOption.None in EF Core
This DatabaseGeneratedOption.None is the default option, indicating that the database
does not generate the property value. It’s typically used for properties you want to set in
your application manually. Use this option if you do not want the database to generate a
value.
This option will be useful to override the default convention for the id properties. If you
remember, for the Id Property or <Entity Name>+Id Property, Entity Framework Makes that
column an Identity Column and automatically generates the value for that column.
For example, we want to provide our own values to Id Property or <Entity Name>+Id
Property instead of autogenerated database values. Then, in this case, we need to use
the DatabaseGeneratedOption.None option. For a better understanding, please modify
745

the Student Entity class as follows. As you can see, we have decorated the StudentId
property with the DatabaseGenerated Attribute with DatabaseGeneratedOption.None value.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Student

[DatabaseGenerated(DatabaseGeneratedOption.None)]

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

}
In this case, the Entity Framework Core will mark the StudentId column in the database as
the Primary Key Column but will not mark this column as an IDENTITY column. So, we
need to specify values for this column while inserting a new record. Next, modify the context
class as shown below. As you can see, we have registered the Student model within the
context class using DbSet.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
746

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMigration1. The name that you are giving it should
not be given earlier.

Now, if you check the Students database table Create SQL SQL Script, you will see it
created the primary key column without Identity, as shown in the image below.
747

Modifying the Main Method:


To prove this, modify the Program class’s Main method as follows. Here, you can see we
are explicitly providing values for the StudentId. If we provide duplicate value or if we don’t
provide any value for the StudentId, then it will throw a Runtime Exception.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

//We need to provide the unique StudentId value

var student1 = new Student() { StudentId = 101, FirstName = "Pranaya", LastName =


"Kumar" };

context.Students.Add(student1);

//We need to provide the unique StudentId value


748

var student2 = new Student() { StudentId = 102, FirstName = "Hina", LastName = "Sharma"
};

context.Students.Add(student2);

//The following Entity will throw an exception as StudentId has duplicate value

//var student3 = new Student() { StudentId = 102, FirstName = "Preety", LastName =


"Tiwari" };

//context.Students.Add(student3);

//The following Entity will throw an exception as we have not supplied value for StudentId

//var student4 = new Student() { FirstName = "Preety", LastName = "Tiwari" };

//context.Students.Add(student4);

context.SaveChanges();

Console.WriteLine("Students Added");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Once you execute the above code, please check the database table, and you should see
the above two students as shown in the below image.
749

DatabaseGeneratedOption.Identity in EF Core
It is also possible to mark the non-key properties as DB-generated properties in Entity
Framework Core using the DatabaseGeneratedOption.Identity option. This option
specifies that the property’s value will be generated automatically by the database on the
INSERT SQL statement. This Identity property cannot be updated.
Please note that how the database will generate the value of the Identity property depends
on the database provider. It can be identity, RowVersion, or GUID. SQL Server makes an
identity column for an integer property.
Let us modify the Student Entity as follows. As you can see here, we have applied
the DatabaseGeneratedOption.Identity option with DatabaseGenerated Attribute on
SequenceNumber property. This will mark the SequenceNumber as the Identity Column in
the database. One more point that you need to remember is in a database table, only one
column can be marked as Identity. So, in this case, StudentId will be the Primary Key
column without Identity, and SequenceNumber will be the Identity column.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Student

[DatabaseGenerated(DatabaseGeneratedOption.None)]

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]

public int SequenceNumber { get; set; }

}
750

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMigration2. The name that you are giving it should
not be given earlier.

Now, if you check the Students database table Create SQL Script, then you will see it
created the primary key column without Identity and SequenceNumber with Identity column
as shown in the below image.

Before proceeding further, let us truncate the Students database table by executing the
following Truncate table statement.
Truncate table Students;
Modifying the Main Method:
Next, we need to modify the Main method of the Program class as follows. Here, you can
see we are explicitly providing values for the StudentId but not for SequenceNumber. If we
provide duplicate value or if we don’t provide any value for the StudentId, then it will throw a
Runtime Exception.
751

using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

//We need to provide unique StudentId value as StudentId is Primary Key

//SequenceNumber is auto generated, so need to provide explicit value

var student1 = new Student() { StudentId = 101, FirstName = "Pranaya", LastName =


"Kumar" };

context.Students.Add(student1);

//We need to provide the unique StudentId value as StudentId is Primary Key

//SequenceNumber is auto generated, so need to provide explicit value

var student2 = new Student() { StudentId = 102, FirstName = "Hina", LastName = "Sharma"
};

context.Students.Add(student2);

context.SaveChanges();

Console.WriteLine("Students Added");

Console.ReadKey();

}
752

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Once you execute the above code, please check the database table, and you should see
the above two students as shown in the below image.

DatabaseGeneratedOption.Compute in EF Core
The DatabaseGeneratedOption.Compute option specifies that the property’s value will be
generated by the database when the value is first saved and subsequently regenerated
every time the value is updated. The practical effect is that Entity Framework will not include
the property in INSERT or UPDATE statements but will obtain the computed value from the
database on retrieval.
Similar to Identity, the way the database generates the value depends on the database
provider. You may configure a default value or use a trigger for this computed column.
Database providers differ in the way that values are automatically generated. Some will
generate values for the Selected Data Types, such as Identity, RowVersion, and GUID.
Others may require manual configuration, such as setting default values or triggers or
configuring the column as Computed.
Before proceeding further, let us truncate the Students database table by executing the
following Truncate table statement.
Truncate table Students;
Let us modify the Student Entity as follows. As you can see here, we have applied
the DatabaseGeneratedOption.Computed option with DatabaseGenerated Attribute
on CreatedDate and FullName properties. This will mark the CreatedDate and FullName
columns as the Computed Column in the database, and for the Computed column, we don’t
need to pass any values.
753

using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

public class Student

[DatabaseGenerated(DatabaseGeneratedOption.None)]

public int StudentId { get; set; }

public string? FirstName { get; set; }

public string? LastName { get; set; }

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]

public int SequenceNumber { get; set; }

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

public DateTime CreatedDate { get; set; }

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

public string? FullName { get; set; }

}
Modifying the Context Class:
We also need to provide some logic so that the values for this column are calculated and
stored in the database automatically. For this, in EF Core, we need to use Fluent API. So,
modify the context class as follows. As you can see inside the OnModelCreating Method,
we use Fluent API to set the computed column values. Here, we have set
the GetUtcDate as the value for the CreatedDate property and First Name + Last
Name as the value for the Full Name Property.
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities
754

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//We will discuss OnModelCreating Method and Fluent API In Details in our upcoming
articles

base.OnModelCreating(modelBuilder);

modelBuilder.Entity<Student>()

.Property(u => u.FullName)

.HasComputedColumnSql("[FirstName] + ' ' + [LastName]");

modelBuilder.Entity<Student>()

.Property(p => p.CreatedDate)

.HasComputedColumnSql("GetUtcDate()");

public DbSet<Student> Students { get; set; }

}
755

Note: In our upcoming articles, we will discuss the OnModelCreating Method and Fluent
API In Detail.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMigration3. The name that you are giving it should
not be given earlier.

Now, if you check the Students database table Create SQL Script, then you will see it
created the primary key column without Identity and SequenceNumber with Identity column,
Fullname, and CreatedDate as Computed Columns, as shown in the below image.

Next, modify the Main method of the Program class as follows:


using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

{
756

internal class Program

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

//We need to provide unique StudentId value as StudentId is Primary Key

//SequenceNumber is auto generated, so need to provide explicit value

//CreatedDate is Computed, so need to provide the value

var student1 = new Student() { StudentId = 101, FirstName = "Pranaya", LastName =


"Kumar" };

context.Students.Add(student1);

context.SaveChanges();

Console.WriteLine("Student Details:");

var student = context.Students.Find(101);

Console.WriteLine($"Id: {student?.StudentId}, FirsName: {student?.FirstName}, FirsName:


{student?.LastName}");

Console.WriteLine($"FullName: {student?.FullName}, SequenceNumber:


{student?.SequenceNumber}, CreatedDate: {student?.CreatedDate}");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;
757

}
Output:

Once you execute the above code, please check the database table, and you should see
the data as expected, as shown in the below image.

Advantages and Disadvantages of DatabaseGenerated Attribute in EF


Core
The DatabaseGenerated attribute in Entity Framework Core (EF Core) allows developers to
specify how values are generated by the database for specific properties of an entity. The
attribute can take values like Identity, Computed, and None from the
DatabaseGeneratedOption enum. Let’s understand the advantages and disadvantages of
using the DatabaseGenerated attribute in EF Core:
Advantages of DatabaseGenerated Attribute in EF Core:
 Automatic Value Generation: It simplifies scenarios where you want the
database to automatically generate values (e.g., identity columns, computed
columns).
 Consistency: By offloading the generation of certain values to the database,
you can ensure consistency, especially if multiple applications or services are
interacting with the same database.
 Concurrency Control: Combined with properties like RowVersion (or
timestamp), DatabaseGenerated can be used for optimistic concurrency
control, helping to prevent conflicts when multiple users or services try to
update the same record simultaneously.
 Optimization: Some databases optimize inserts for identity columns or have
specialized functions to efficiently generate certain types of values (e.g.,
NEWID() or NEWSEQUENTIALID() in SQL Server for GUIDs).
 Reduced Application Complexity: Instead of writing custom code to
generate values or compute certain properties, you can delegate that
responsibility to the database.
 Data Integrity: Ensures that certain fields (like identity columns) are unique
and are not accidentally overwritten or mismanaged by application logic.
758

Disadvantages of DatabaseGenerated Attribute in EF Core:


 Database Dependency: Your application’s logic becomes tightly coupled
with specific database behaviors, potentially making it harder to switch
databases or work with multiple database providers.
 Reduced Flexibility: In some scenarios, having the database generate
values might reduce flexibility, especially if you need to enforce business rules
or complex logic during value generation.
 Potential Performance Overhead: Some database-generated operations
might introduce performance overhead, especially if not properly optimized
(e.g., non-sequential GUID generation causing index fragmentation).
 Migration Concerns: Changing how values are generated (e.g., from client-
side to database-generated or vice versa) can introduce complexities in
database migrations.
 Debugging and Troubleshooting: When values are generated or computed
by the database, it can sometimes be harder to troubleshoot issues or bugs
related to those values, as the logic is not present in the application layer.
 Concurrency Issues with Computed Values: If not handled properly,
relying on database-computed values can sometimes lead to concurrency
issues, especially if multiple operations depend on the computed value.

TimeStamp Attribute in Entity Framework Core (EF Core)


In this article, I will discuss TimeStamp Data Annotation Attribute in Entity Framework
Core (EF Core) with Examples. Please read our previous article, which
discussed DatabaseGenerated Attribute in Entity Framework Core with Examples.
TimeStamp Attribute in Entity Framework Core
In Entity Framework Core (EF Core), the Timestamp attribute is used to specify that a
particular byte array property should be treated as a concurrency token. A concurrency
token is a value used to ensure that the data being updated or deleted has not changed
since it was last read, providing a way to manage concurrent operations on the data.
When using the Timestamp attribute, EF Core will configure the property as a RowVersion
column in SQL Server (and analogous types in other databases). The database
automatically updates the RowVersion every time a row is updated, which allows EF Core
to detect concurrent edits.
The TimeStamp Data Annotation Attribute in Entity Framework Core can only be applied
once to a byte array type property in an entity class. It creates a column with timestamp
data type in the SQL Server database. EF Core will automatically use this Timestamp
column in concurrency check on the UPDATE SQL Statement in the database. The
TimeStamp attribute can only be applied to a byte[] array type property. It cannot be applied
to other data types.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:14 / 03:1710 Sec
If you go to the definition of TimeStamp Attribute in EF Core, you will see the following. As
you can see, this class has a parameterless constructor.
759

So, using the Timestamp Attribute in Entity Framework Core is one of the ways to handle
the Concurrency Issues. The Concurrency issue arises when multiple users or transactions
attempt to update/delete the same row simultaneously. We can handle this Concurrency
issue using the Timestamp column. TimeStamp can also be referred to as RowVersion.
What is TimeStamp or RowVersion in SQL Server?
In SQL Server, ROWVERSION and TIMESTAMP are Synonyms. The ROWVERSION has
been available since SQL Server 2005, while TIMESTAMP is deprecated and will be
removed in a future version of SQL Server.
ROWVERSION (TIMESTAMP) is an incrementing 8-byte binary number that does not
store any DateTime-related information. In SQL Server, ROWVERSION and TIMESTAMP
data types represent automatically generated unique binary numbers within the database.
This helps us maintain the integrity of the database when multiple users are updating and
deleting rows simultaneously.
Suppose a table contains a ROWVERSION (or TIMESTAMP) column. In that case, any
time a new row is inserted or any existing row is updated, the value of the ROWVERSION
(or TIMESTAMP) column is set to the current ROWVERSION (or TIMESTAMP) value. This
is true even when an UPDATE statement does not change the data in the database.
Example to Understand Timestamp Attribute in EF Core:
Let us understand the Timestamp Attribute in Entity Framework Core with an example.
Please modify the Student Entity class as follows. Here, we have applied the TimeStamp
Attribute of the RowVersion Property, which is byte[] array type. In an Entity, we can only
apply the TimeStamp Attribute once. The database will create the column with
the TimeStamp data type.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? FirstName { get; set; }


760

public string? LastName { get; set; }

//We cannot have more than Column with [TimeStamp] Attribute

//One Table Can have only one TimeStamp Column

[Timestamp]

public byte[]? RowVersion { get; set; }

}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

{
761

public DbSet<Student> Students { get; set; }

}
Next, modify the Main Method of the Program class as follows. Here, you can see we have
added two entities to the database. The point that you need to remember is that the
database autogenerates the TimeStamp value. So, we do not need to set the value for that
property.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

var student1 = new Student() { FirstName = "Pranaya", LastName = "Kumar" };

context.Students.Add(student1);

var student2 = new Student() { FirstName = "Hina", LastName = "Sharma" };

context.Students.Add(student2);

context.SaveChanges();

Console.WriteLine("Students Added");

Console.ReadKey();

}
762

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

If you verify the database, the RowVersion column should be created with the TimeStamp
data type, as shown in the image below.
763

Now run the application and then verify the Students table. You will see that the auto-
incrementing 8-byte binary number generated, unique within the database, will be stored in
the RowVersion column, as shown in the image below.

The timestamp column will be included in the where clause whenever we update an entity
and call the SaveChanges method. We have already added two students with StudentId 1
and 2. Let us update the student’s first and last names, whose ID is 1. To do so, modify the
Main Method of the Program class as follows.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

{
764

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

//Fetch the Student Details whose Id is 1

Student? studentId1 = context.Students.Find(1);

if(studentId1 != null)

studentId1.FirstName = "First Name Updated";

studentId1.LastName = "Last Name Updated";

context.SaveChanges();

Console.WriteLine("Student Updated");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
765

When you run the above code, you will get the following output. Here, you can see that the
where clause and the Primary Key Column also use the RowVersion column within the
UPDATE SQL Statement. This is to handle the concurrency issues.

If you verify the database, the RowVersion column should be updated with the latest
increment value with Student ID 1, as shown in the image below.

Handling Concurrency Issues with EF Core using TimeStamp Attribute:


The primary use of the Timestamp attribute is for optimistic concurrency control. When an
entity is updated, EF Core includes the RowVersion property in the WHERE clause of the
update statement. If no rows are affected (indicating that the RowVersion has changed in
the database since the entity was last fetched), EF Core throws a
DbUpdateConcurrencyException. This means another user or process has modified the
record, and you can handle this exception as appropriate for your application.
RowVersion (Timestamp) is a SQL column type that uses auto-generated unique binary
numbers across that database. Any time a record is inserted or updated on a table with a
row version, a new unique number is generated (in binary format) and given to that record.
Again, the RowVersions are unique across that entire database, not just the table.
766

Let us understand how to handle concurrency issues with Entity Framework Core. Please
modify the Main Method of the Program class as follows. Here, we are updating the same
entity using two different threads simultaneously. Both Method1 and Method2 read the
student entity whose ID is 1 and also read the same RowVersion value.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

Console.WriteLine("Main Method Started");

Thread t1 = new Thread(Method1);

Thread t2 = new Thread(Method2);

t1.Start();

t2.Start();

t1.Join();

t2.Join();

Console.WriteLine("Main Method Completed");


767

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

//USER 1 | Transaction 1

public static void Method1()

using EFCoreDbContext context = new EFCoreDbContext();

//Fetch the Student Details whose Id is 1

var studentId1 = context.Students.Find(1);

//Before Updating Delay the Thread by 2 Seconds

Thread.Sleep(TimeSpan.FromSeconds(2));

if (studentId1 != null)

studentId1.FirstName = studentId1.FirstName + "Method1";

studentId1.LastName = studentId1.LastName + "Method1";

context.SaveChanges();

Console.WriteLine("Student Updated by Method1");

//USER 2 | Transaction 2
768

public static void Method2()

using EFCoreDbContext context = new EFCoreDbContext();

//Fetch the Student Details whose Id is 1

var studentId1 = context.Students.Find(1);

//Before Updating Delay the Thread by 5 Seconds

Thread.Sleep(TimeSpan.FromSeconds(5));

if (studentId1 != null)

studentId1.FirstName = studentId1.FirstName + "Method2";

studentId1.LastName = studentId1.LastName + "Method2";

context.SaveChanges();

Console.WriteLine("Student Updated by Method2");

}
Let us assume Method 1 starts updating first. So, he will update the data in the database
and the RowVersion value. Now, Method2 tries to update the same entity. If you remember,
it will use the RowVersion column while updating, but that RowVersion value with the
Method2 entity has already been modified by Method1. So, Method2 has a RowVersion
value that no longer exists in the database; hence, the Method2 SaveChanges method will
throw an exception showing concurrency issues.
769

If you verify the database, you will see the updated data with the updated RowVersion value
for the student whose ID is 1, as shown in the image below. So, in this way, TimeStamp
Attribute handles the concurrency issues. Each time you perform an INSERT or UPDATE
Operation, the RowVersion value will increment.

Advantages and Disadvantages of TimeStamp Attribute in Entity


Framework Core
The Timestamp attribute in Entity Framework Core (EF Core) designates a byte array
property as a concurrency token. This token typically maps to the RowVersion column type
in databases like SQL Server, and its primary purpose is to handle optimistic concurrency
control. Let’s explore the advantages and disadvantages of using the Timestamp attribute in
EF Core:
Advantages of TimeStamp Attribute in Entity Framework Core:
 Optimistic Concurrency Control: The Timestamp attribute provides an
automated way to manage optimistic concurrency, ensuring that data
changes do not conflict with one another.
 Automatic Database Management: When mapped to a RowVersion or
equivalent column, the database automatically manages the updates of this
column, ensuring a unique value for each modification.
 Data Integrity: Helps prevent scenarios where one user unknowingly
overwrites changes made by another user.
770

 Performance: Offers a lightweight mechanism for concurrency control


compared to tracking and comparing all individual properties of an entity.
 Simplified Conflict Detection: When a concurrency conflict arises, EF Core
throws a DbUpdateConcurrencyException, making detecting and handling
these situations in the application logic relatively straightforward.
 Less Boilerplate: By automating concurrency checks, developers can avoid
manually writing code to handle these checks for each entity.
Disadvantages of TimeStamp Attribute in Entity Framework Core:
 Additional Database Column: It introduces an extra column, which might
not be desired in all scenarios, especially if the database is not under EF
Core’s full control or if there are storage concerns.
 Limited Scope: The Timestamp attribute is specifically for concurrency
control. It cannot store actual timestamp data or other meaningful values.
 Database Dependency: While RowVersion is a standard feature in SQL
Server, the behavior or equivalent might differ across other databases. This
can lead to potential issues if you plan to switch or support multiple database
providers.
 Error Handling Overhead: While DbUpdateConcurrencyException makes it
easier to detect conflicts, developers must still write appropriate error-
handling code to manage these exceptions and potentially present them in a
user-friendly manner.
 Potential Confusion: New developers or those unfamiliar with EF Core
might find using the Timestamp attribute confusing, especially since it doesn’t
store an actual timestamp.
While the Timestamp attribute provides a powerful and efficient way to handle optimistic
concurrency in EF Core, it’s essential to understand its purpose and limitations. When used
correctly, it can greatly enhance the integrity and consistency of data in scenarios with
concurrent data access. However, it’s important to consider the specific requirements of
your application and database to determine if it’s the right choice for your scenario.

ConcurrencyCheck Attribute in Entity Framework Core


In this article, I will discuss ConcurrencyCheck Data Annotation Attribute in Entity
Framework Core (EF Core) with Examples. Please read our previous article
discussing TimeStamp Attribute in Entity Framework Core with Examples. The
[ConcurrencyCheck] attribute is used in Entity Framework Core to mark a property or
properties being used for optimistic concurrency control. This attribute helps Entity
Framework Core determine whether a concurrent update has occurred on an entity while
it’s being saved to the database.
ConcurrencyCheck Attribute in Entity Framework Core
The ConcurrencyCheck Data Annotation Attribute can be applied to one or more properties
of an entity in Entity Framework Core. When we apply the ConcurrencyCheck Attribute to a
property, then the corresponding column in the database table will be used in the optimistic
concurrency check using the where clause.
771

So, using the ConcurrencyCheck Attribute in Entity Framework Core is another way to
handle the Concurrency Issues. The Concurrency issue arises when multiple users or
transactions attempt to update/delete the same row simultaneously. We can handle this
Concurrency issue using the ConcurrencyCheck column.
In Entity Framework Core (EF Core), the ConcurrencyCheck attribute indicates that a
particular property should be included in concurrency conflict detection. When a property is
marked with this attribute, EF Core will include it in the WHERE clause of UPDATE and
DELETE commands, ensuring that the value hasn’t changed since the entity was fetched.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:11 / 03:1710 Sec
If you go to the definition of ConcurrencyCheck Attribute in EF Core, you will see the
following. As you can see, this class has a parameterless constructor.

Example to Understand ConcurrencyCheck Attribute in EF Core:


Let us understand ConcurrencyCheck Data Annotation Attribute in Entity Framework Core
with an example. Please modify the Student Entity class as follows. We have applied the
ConcurrencyCheck Attribute on the Name and RegdNumber Properties here. In EF Core,
we can apply the ConcurrencyCheck Attribute with one or more properties of an Entity.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

[ConcurrencyCheck]

public int RegdNumber { get; set; }

[ConcurrencyCheck]
772

public string? Name { get; set; }

public string? Branch { get; set; }

}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated the Database Script

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
773

}
Next, modify the Main Method of the Program class as follows. Here, you can see we have
added two entities to the database. Unlike TimeStamp, we must provide values for
[ConcurrencyCheck] attribute properties.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

var student1 = new Student() { Name = "Pranaya", Branch = "CSE", RegdNumber = 1001 };

context.Students.Add(student1);

var student2 = new Student() { Name = "Hina", Branch = "CSE", RegdNumber = 1002 };

context.Students.Add(student2);

context.SaveChanges();

Console.WriteLine("Students Added");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;
774

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS
and Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, run the application, and you will see the records are being inserted into the Students
database table as expected, as shown in the image below.

The ConcurrencyCheck column(s) will be included in the where clause whenever we update
or delete an entity and call the SaveChanges method. We have already added two students
with StudentId 1 and 2. Let us update the Name and Branch of the Student whose ID is 1.
To do so, modify the Main Method of the Program class as follows.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

{
775

internal class Program

static void Main(string[] args)

try

using EFCoreDbContext context = new EFCoreDbContext();

//Fetch the Student Details whose Id is 1

var studentId1 = context.Students.Find(1);

if (studentId1 != null)

studentId1.Name = "Name Updated";

studentId1.Branch = "Branch Updated";

context.SaveChanges();

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
776

When you run the above code, you will get the following output. Here, you can see, in the
where clause, along with the Primary Key Column, it also uses the Name and RegdNumber
columns. This is to handle the concurrency issues.

Handling Concurrency Issues using ConcurrencyCheck in Entity


Framework:
A concurrency token is a value that checks a database record for updates. If the value has
changed, the update will fail. This can happen when two users edit the same record
simultaneously.

Let us understand how to handle concurrency issues with Entity Framework using the
ConcurrencyCheck column. Please modify the Main Method of the Program class as
follows. Here, we are updating the same student using two different threads simultaneously.
Both Method1 and Method2 read the student entity whose ID is 1 and also read the same
Name and RegdNumber values.
Let us assume Method 1 starts updating first. So, he will update the data in the database
and the Name and RegdNumber column values. Now, Method2 tries to update the same
entity. If you remember, while updating, it will use the Name and RegdNumber columns in
the where clause, but Method1 has already modified the Name and RegdNumber column
value with the Method2 entity. So, Method2 has Name and RegdNumber values that no
longer exist in the database, and hence Method2 SaveChanges method will throw an
exception showing concurrency issues. It might be possible that thread2 starts its execution
first; in that case, Method1 will throw an exception.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

{
777

internal class Program

static void Main(string[] args)

try

Console.WriteLine("Main Method Started");

Thread t1 = new Thread(Method1);

Thread t2 = new Thread(Method2);

t1.Start();

t2.Start();

t1.Join();

t2.Join();

Console.WriteLine("Main Method Completed");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void Method1()

using EFCoreDbContext context = new EFCoreDbContext();


778

//Fetch the Student Details whose Id is 1

var studentId1 = context.Students.Find(1);

//Before Updating Delay the Thread by 2 Seconds

Thread.Sleep(TimeSpan.FromSeconds(2));

if (studentId1 != null)

studentId1.Name = studentId1.Name + "Method1";

studentId1.Branch = studentId1.Branch + "Method1";

context.SaveChanges();

Console.WriteLine("Student Updated by Method1");

public static void Method2()

using EFCoreDbContext context = new EFCoreDbContext();

//Fetch the Student Details whose Id is 1

var studentId1 = context.Students.Find(1);

//Before Updating Delay the Thread by 2 Seconds

Thread.Sleep(TimeSpan.FromSeconds(2));

if (studentId1 != null)

studentId1.Name = studentId1.Name + " Method2";

studentId1.Branch = studentId1.Branch + " Method2";

context.SaveChanges();
779

Console.WriteLine("Student Updated by Method2");

}
You will get the following exception when you run the above application, which makes
sense.

Advantages and Disadvantages of ConcurrencyCheck Attribute in Entity


Framework Core
The ConcurrencyCheck attribute in Entity Framework Core (EF Core) enables developers to
define properties that should be considered in concurrency conflict detection. This offers
more control over how EF Core detects and manages concurrent edits on data. Let’s delve
into the advantages and disadvantages of using the ConcurrencyCheck attribute:
Advantages of ConcurrencyCheck Attribute in Entity Framework Core:
 Granular Concurrency Control: Provides fine-grained control over which
specific properties of an entity should be considered for concurrency checks.
This is useful for targeting fields more likely to have concurrent updates.
 Intuitive Mapping: For some application scenarios, checking specific fields
for concurrency issues is more intuitive. For instance, detecting if a shared
document’s content or title has changed in a collaborative application can be
more relevant than a generic timestamp.
780

 Flexibility: Multiple properties within an entity can be marked with the


ConcurrencyCheck attribute, offering a broader range of checks for potential
data conflicts.
 Optimistic Concurrency: Helps in implementing optimistic concurrency
control without needing a separate RowVersion or timestamp column.
Disadvantages of ConcurrencyCheck Attribute in Entity Framework Core:
 Performance Implications: Adding the ConcurrencyCheck attribute to
multiple properties can lead to more extensive WHERE clauses in UPDATE
and DELETE SQL statements. This might affect performance, especially in
large tables or when many properties are marked for concurrency checks.
 Increased Conflict Potential: By checking specific property values, there’s
an increased chance of detecting conflicts, even in scenarios where changes
by different users might be logically compatible but still trigger a conflict.
 Complex Conflict Resolution: Resolving detected conflicts can become
more complicated based on specific properties rather than a general
timestamp or version number.
 Database Schema: Unlike a Timestamp field, which might be used
exclusively for concurrency control, fields marked with ConcurrencyCheck
often have domain significance. Changes to the semantics or usage of these
fields can inadvertently impact concurrency behavior.
 Maintenance Overhead: As the domain model evolves, developers must
consistently review and possibly update which properties should have the
ConcurrencyCheck attribute, ensuring the right fields are checked for
concurrency conflicts.
While the ConcurrencyCheck attribute provides a nuanced approach to managing optimistic
concurrency, it comes with challenges. It’s essential to weigh its benefits against its
complexities, keeping in mind the application’s specific requirements and the frequency of
concurrent edits on data. The choice of ConcurrencyCheck should be driven by the
application’s domain needs and the desired granularity of concurrency control.
Differences Between TimeStamp and ConcurrencyCheck Attribute in EF
Core:
In Entity Framework Core, the [Timestamp] and [ConcurrencyCheck] attributes relate to
optimistic concurrency control but serve slightly different purposes.
[Timestamp] Attribute:
The [Timestamp] attribute is used to specify that a property in an entity class represents a
timestamp or row version column in the database. This attribute is typically applied to a
byte[] property. The database automatically updates this property’s value whenever a row is
modified.
The [Timestamp] attribute is mainly used to detect conflicts between concurrent updates.
When you save changes to an entity, Entity Framework Core checks if the current value of
the timestamp property in the database matches the value originally retrieved when the
entity was loaded. If the values don’t match, another process updates the same record in
the database.
[ConcurrencyCheck] Attribute:
The [ConcurrencyCheck] attribute marks a property used for optimistic concurrency control,
indicating that this property should be considered when checking for conflicts during
updates. This attribute can be applied to any numeric, datetime, or byte array property.
When an entity is saved, Entity Framework Core compares the current value of the
781

[ConcurrencyCheck]-marked property or properties with the originally retrieved value. If the


values don’t match, a concurrency conflict is detected.
Key Differences:
Data Type:
Timestamp: Applied to a byte[] property in your model. This property generally corresponds
to a RowVersion column in SQL Server or its equivalent in other databases.
ConcurrencyCheck: Can be applied to any property of an entity, not limited to a specific
data type.
Database Behavior:
Timestamp: In databases like SQL Server, columns mapped from properties marked with
Timestamp are automatically updated with a new value every time there is an update to the
row. The database handles this auto-increment behavior.
ConcurrencyCheck: This does not automatically change the value. Instead, the property’s
current value is checked against the database value during an update or delete operation.
Granularity:
Timestamp: Provides a single point of truth for concurrency checks. If any part of the row
changes, the timestamp (RowVersion) value will change.
ConcurrencyCheck: Offers fine-grained control. Developers can specify which individual
properties should be considered for concurrency checks.
Conflict Detection:
Timestamp: Conflicts are detected if the stored RowVersion value differs from the one the
application provides during an update or deletion.
ConcurrencyCheck: Conflicts are detected if the stored value of any property marked with
ConcurrencyCheck differs from the current value the application provides.
Storage Overhead:
Timestamp: Requires an additional RowVersion column in the database, which consumes
8 bytes of storage.
ConcurrencyCheck: Does not introduce additional columns. However, the properties you
mark can be of any size or type, possibly leading to larger WHERE clauses in update/delete
statements.
Applicability:
Timestamp: Typically applied once per entity to handle concurrency for the entire entity.
ConcurrencyCheck: This can be applied to multiple properties within an entity, each acting
as a separate point of concurrency check.
Flexibility and Semantics:
Timestamp: The value is purely for concurrency control and doesn’t hold domain-specific
semantics.
ConcurrencyCheck: The properties marked often have domain significance. Their primary
purpose isn’t just concurrency control; they are used in that capacity when marked.
782

Fluent API in Entity Framework Core


In this article, I will discuss How to Implement Fluent API Configurations in Entity
Framework Core (EF Core) with Examples. Please read our previous article
discussing ConcurrencyCheck Attribute in Entity Framework Core with Examples.
Fluent API in Entity Framework Core:
Like the Data Annotation Attribute, the Entity Framework Core provides Fluent API, which
we can also use to configure the domain classes, which will override the default
conventions that the Entity Framework Core follows.
Fluent API in Entity Framework Core (EF Core) provides a way to configure the model
classes and relationships between them using C# code rather than attributes. This allows
for a more detailed level of configuration that might not be possible using only Data
Annotation Attributes. The Fluent API configurations typically reside in
the OnModelCreating method of our DbContext subclass.
The EF Core Fluent API is based on the Fluent API Design Pattern (AKA Fluent
Interface Design Pattern), where Method Chaining formulates the result. So, before
proceeding further and understanding the Fluent API in EF Core, we first need to
understand the Fluent Interface Design Pattern and Method Chaining in C#.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec

What is the Fluent Interface Design Pattern?


The Fluent Interface Design Pattern is a software design pattern that creates a more
readable and expressive API by allowing method chaining. It aims to make the code more
intuitive and natural to read, often resembling sentences or phrases in human language.
In C#, a fluent interface is usually implemented by returning the instance of the object being
manipulated from each method call, which allows subsequent methods to be chained
together. This pattern is commonly used in libraries and APIs to provide a concise and fluid
way of configuring or interacting with objects.
The main objective of the Fluent Interface Design Pattern is to apply multiple methods to an
object by connecting them with dots (.) without having to re-specify the object name each
time. Let us understand How to Implement a Fluent Interface Design Pattern with an
example. Let us say we have the following Student class.
783

Now, if we want to consume the above Student class, we generally create an instance of
the Student class, and then we need to set the respective properties of the Student class as
shown in the image below.

The Fluent Interfaces Design Pattern simplifies our object consumption code by making our
code more simple, readable, and understandable. Is it not nice to set the Student object
properties as shown in the below image?

Consuming the object like the above interface is like speaking a sentence that would make
the class consumption code more simple, readable, and understandable. The next big thing
we must understand is how to implement this. To implement this, we have something called
Method Chaining.
What is Method Chaining?
Method chaining is nothing but a technique or process where each method sets the value of
a property of an object and then returns an object, and all these methods can be chained
together to form a single statement. To implement method chaining, first, we must create a
wrapper class around the Student class, as shown in the image below.
784

As you can see in the above FluentStudent class, we have created methods for each
Student property. First, we create an instance of Student Class, and then, in each method,
we set the value of the respective Student Property. Further, notice that the return type of
each method is set to the FluentStudent, which is important; because of this, we can call
subsequent methods using the dot operator. Now, the above fluent interface is going to be
consumed by the client. So, with the above FluentStudent class in place, the client code
looks as shown below.
785

The Complete Example Code of Fluent Interface Design Pattern:


Whatever we have discussed so far is given in the below example. The following example
shows how to implement a Fluent Interface Design Pattern in C#.
using System;

namespace FluentInterfaceDesignPattern

public class Program

static void Main(string[] args)

FluentStudent student = new FluentStudent();

student.StudentRegedNumber("BQPPR123456")

.NameOfTheStudent("Pranaya Rout")

.BornOn("10/10/1992")

.StudyOn("CSE")

.StaysAt("BBSR, Odisha");

Console.Read();

public class Student


786

public string RegdNo { get; set; }

public string Name { get; set; }

public DateTime DOB { get; set; }

public string Branch { get; set; }

public string Address { get; set; }

public class FluentStudent

private Student student = new Student();

public FluentStudent StudentRegedNumber(string RegdNo)

student.RegdNo = RegdNo;

return this;

public FluentStudent NameOfTheStudent(string Name)

student.Name = Name;

return this;

public FluentStudent BornOn(string DOB)

student.DOB = Convert.ToDateTime(DOB);

return this;
787

public FluentStudent StudyOn(string Branch)

student.Branch = Branch;

return this;

public FluentStudent StaysAt(string Address)

student.Address = Address;

return this;

}
Advantages and Disadvantages of the Fluent Interface Design Pattern:
Advantages of the Fluent Interface Design Pattern:
1. Readability: Method chaining provides a natural, readable flow, often
resembling a sequence of statements or commands.
2. Conciseness: Fluent syntax can make the code more concise and
expressive.
3. Discoverability: The fluent interface often guides users toward available
options and actions.
4. Intuitive: The syntax can be more intuitive, especially for configuring or
building complex objects.
Disadvantages of the Fluent Interface Design Pattern:
1. Learning Curve: While fluent interfaces can make code more expressive,
they might have a learning curve for those unfamiliar with the pattern.
2. Complexity: Overuse or excessive method chaining can lead to overly
complex and difficult-to-debug code.
3. Immutability: Fluent interfaces may not be suitable for cases where object
immutability is crucial.
Now, I hope you understand the Fluent Interface Design Pattern. With this kept in mind, let
us proceed and try to understand Fluent API in Entity Framework Core.
Configuring Fluent API in Entity Framework Core:
788

The point that you need to remember is Fluent API configuration can only be applied when
Entity Framework Core builds the models from your domain classes. That means you can
use Fluent API Only when you are working with EF Core Code First Approach.
In EF Core, the DbModelBuilder class acts as a Fluent API, using which we can configure
many different things. It provides more options for configurations than Data Annotation
attributes.
In this case, we need to override the OnModelCreating method of the DbContext class in
our context class to inject the Fluent API configurations, something like the one below in
Entity Framework Core. We call many methods using the same modelBuilder object using
the dot (.) operators, which is nothing but method chaining.

Note: The point that you need to remember is that, in Entity Framework Core, you can
configure a domain class using both Data Annotation Attributes and Fluent API
simultaneously. In this case, EF Core will give precedence to Fluent API over Data
Annotations Attributes.
Fluent API Configurations in Entity Framework Core:
In EF Core, the Fluent API configures the following things of a model class.
1. Model-Wide Configuration: Configures an EF model to database mappings.
Configures the default Schema, DB functions, additional data annotation
attributes, and entities to be excluded from mapping.
2. Entity Configuration: Configures entity to table and relationships mapping.
For example, PrimaryKey, AlternateKey, Index, table name, one-to-one, one-
to-many, many-to-many relationships, etc.
3. Property Configuration: Configures property to column mapping. For
example, column name, default value, nullability, Foreignkey, data type,
concurrency column, etc.
Now, let us proceed and try to understand the different methods available in each
configuration.
Model-Wide Configurations in EF Core
789

1. HasDbFunction(): Configures a database function when targeting a


relational database.
2. HasDefaultSchema(): Specifies the database schema.
3. HasAnnotation(): Adds or updates data annotation attributes on the entity.
4. HasSequence(): Configures a database sequence when targeting a
relational database.
Entity Configurations in EF Core:
1. HasAlternateKey(): Configures an alternate key in the EF model for the
entity.
2. HasIndex(): Configures an index of the specified properties.
3. HasKey(): Configures the property or list of properties as Primary Key.
4. HasMany(): Configures the Many parts of the relationship, where an entity
contains the reference collection property of other types for one-to-many or
many-to-many relationships.
5. HasOne(): The HasOne() method specifies the navigation property
representing the relationship’s principal end.
6. WithOne(): The WithOne() method specifies the navigation property on the
dependent end.
7. HasForeignKey<TDependent>(): The HasForeignKey<TDependent>()
method is used to specify which property in the dependent entity represents
the foreign key.
8. HasMany(): Configures a relationship where this entity type has a collection
that contains instances of the other type in the relationship. That means it
specifies the ‘many’ side of the relationship.
9. Ignore(): Configures that the class or property should not be mapped to a
table or column.
10. ToTable(): Configures the database table that the entity maps to.
Property Configurations in Entity Framework:
1. HasColumnName(): Configures the corresponding column name in the
database for the property.
2. HasColumnType(): Configures the data type of the corresponding column in
the database for the property.
3. HasComputedColumnSql(): Configures the property to map to computed
columns when targeting a relational database.
4. HasDefaultValue(): Configures the default value for the column the property
maps to when targeting a relational database.
5. HasDefaultValueSql(): Configures the default value expression for the
column the property maps to when targeting a relational database.
6. HasField(): Specifies the backing field for the property.
7. HasMaxLength(): Configures the maximum data length stored in a property.
8. IsConcurrencyToken(): Configures the property to be used as an optimistic
concurrency token.
9. IsRequired(): Configures whether the valid value of the property is required
or whether null is a valid value.
790

10. IsRowVersion(): Configures the property for optimistic concurrency


detection.
11. IsUnicode(): Configures the string property, which can contain Unicode
characters or not.
12. ValueGeneratedNever(): Configures a property that cannot have a
generated value when an entity is saved.
13. ValueGeneratedOnAdd(): Configures that the property has a generated
value when saving a new entity.
14. ValueGeneratedOnAddOrUpdate(): Configures that the property has a
generated value when saving a new or existing entity.
15. ValueGeneratedOnUpdate(): Configures that a property has a generated
value when saving an existing entity.
Note: In our upcoming article, I will explain all the above Fluent API Configuration Methods
with Examples using Entity Framework Core.
Advantages and Disadvantages of Fluent API Configurations in Entity
Framework Core:
Fluent API configurations in Entity Framework Core provide a programmatic way to
configure your model to database mappings. Let us see the advantages and disadvantages
of Fluent API Configurations in Entity Framework Core.
Advantages of Fluent API Configurations in Entity Framework Core:
 Granular Configuration: Fluent API provides a detailed configuration level,
which is impossible with Data Annotations alone.
 Separation of Concerns: By keeping configuration separate from the
domain classes, our entity classes can remain POCOs (Plain Old CLR
Objects) without any EF-specific attributes, making them cleaner.
 Unified Configuration Location: Configurations are centralized in one place
(typically in the OnModelCreating method), which makes it easier to get an
overview of all configurations and relationships between entities.
 Advanced Mappings: Fluent API provides more options for configuring
complex relationships, inheritance mapping strategies, and other advanced
features.
 No External Dependencies: Since Fluent API doesn’t require attributes,
there’s no need to reference EF assemblies in domain projects if they are
separated.
 Dynamic Configurations: Programmatic configurations can be dynamic
based on conditions, whereas Data Annotations are static.
Disadvantages of Fluent API Configurations in Entity Framework Core:
 Learning Curve: Fluent API might have a steeper learning curve than
straightforward Data Annotations for newcomers.
 Verbosity: Fluent API can be more verbose. For simple configurations, Data
Annotations can be more concise.
 Separation from Entity: Since the configuration is separate from the entity
class, developers might need to jump back and forth between the entity class
and configuration to understand or modify the mapping.
 Discoverability: For new developers or developers unfamiliar with Fluent
API, it might be less discoverable compared to seeing attributes directly on
properties in an entity class.
791

 Potential for Mistakes: Due to the detailed nature of Fluent API, there’s a
higher chance of making mistakes, especially when configuring complex
relationships.
 Maintenance: As your application grows, the OnModelCreating method can
become large and unwieldy, making maintenance challenging.
In Real-Time Applications, many teams find a balance by combining Fluent API and Data
Annotations. Simple configurations might be handled with annotations for clarity and brevity.
In contrast, Fluent API can do more complex configurations or those that need to be
separated from the entity class. The choice ultimately depends on the specific needs of the
project and the team’s preferences.

Relationships Between Entities in Entity Framework Core


In this article, I will discuss the Relationships Between Entities in the Entity Framework
Core, i.e., at the end of this article, you will understand how the entity framework manages
the relationships between entities. Please read our previous article discussing Fluent API
Configurations in Entity Framework Core with Examples.
What are Relationships?
A common requirement in relational databases is establishing relationships between
database tables or entities. Whenever we try to establish a relationship between two
entities, then one of the entities will act as a Principal Entity, and the other Entity will act as
the Dependent Entity.
Let us first understand what is Principal Entity and Dependent Entity from the relational
database point of view.
1. Principal Entity: The Entity containing the Primary or Unique Key properties
is called Principal Entity.
2. Dependent Entity: The Entity which contains the Foreign key properties is
called Dependent Entity.
From the Relational Database Point of view, we already know that the Relationships
Between two entities or tables are established using Foreign Keys. A Foreign Key is a Key
that refers to the Unique Key or Primary Key of another table. Once we establish the
relationships between database tables or entities, then we can get meaningful results from
the database.
Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
According to Database Normalization, we should not store all the information in a single
database table. We need to split the data into multiple tables, but there should be some link
or relationship between the database tables.
Before understanding the different types of relationships, let us first understand a few
important terms used in database and model classes.
1. Primary Key: The Primary key is a column (columns in the case of
composite primary key) in the database Table that Uniquely identifies each
row.
2. Foreign Key: The Foreign key is a column in a table that makes a
relationship with another table. The Foreign key Column should point to the
Principal Entity’s Primary Key or Unique Key column.
792

3. Navigation Properties: In .NET, the Navigation properties define the type of


relationships between the Entities. Based on the requirements, these
properties are defined either in the Principal Entity or in the Dependent Entity.
The Navigation Properties are again classified into two types. They are as follows:
1. Reference Navigation Property: This property refers to a Single Related
Entity, i.e., it is used to implement a One-to-One relationship between two
entities.
2. Collection Navigation Property: This property refers to a Collection of
Entities, i.e., it is used to implement One-to-Many or Many-to-Many
relationships between two entities.
Types of Relationships in Database:
In a relational database, a relationship refers to the association or connection between two
tables based on common data elements. Relationships are fundamental to organizing and
structuring data to enable efficient querying and retrieval of information. They define how
data in different tables are related to each other, allowing us to perform complex queries
and analyze the data effectively. There are several types of relationships in a relational
database. They are as follows:
1. One-to-One (1:1) Relationship: In this type of relationship, each record in
one table is associated with exactly one record in another table, and vice
versa. This is often used when two entities have a unique relationship.
2. One-to-Many (1:N) Relationship: In a one-to-many relationship, a single
record in one table is related to multiple records in another table. However,
each record in the second table is related to only one record in the first table.
This is the most common type of relationship used in real-time applications.
3. Many-to-Many (M:N) Relationship: A many-to-many relationship exists
when multiple records in one table are related to multiple records in another
table. This type of relationship is typically implemented using a junction table
or associative entity, which breaks down the relationship into two one-to-many
relationships.
4. Self-Referencing Relationship: This type of relationship occurs when
records in a single table are related to other records in the same table. For
example, in an organizational structure, an employee might have a manager
who is also an employee.
Properly defining and maintaining relationships in a database is crucial for data integrity,
consistency, and accuracy. It allows us to avoid data duplication, reduce redundancy, and
create a well-structured and normalized database schema. Relational databases, such as
MySQL, Oracle, PostgreSQL, and Microsoft SQL Server, provide mechanisms for defining
and enforcing these relationships through their table creation and modification features.
Types of Relationships in Entity Framework Core:
In relational databases, there are four types of relationships between the database tables
(One-to-One, One-to-Many, Many-to-Many, and Self-Referencing Relationship).
Entity Framework Core (EF Core) is an Object-Relational Mapping (ORM) framework
for .NET that enables developers to work with databases using object-oriented concepts.
EF Core uses navigation properties and annotations to define relationships between entities
(classes that represent database tables). The Entity Framework supports four types of
relationships similar to the database. They are as follows:
1. One-to-One Relationship
793

2. One-to-Many Relationship
3. Many-to-Many Relationship
4. Self-Referencing Relationship
One-to-One (1:1) Relationship:
EF Core Establishes a One-to-One Relationship when both entities have a reference
navigation property pointing to the other entity. Annotations like [ForeignKey] and
[InverseProperty] are often used to define the relationship explicitly. In this relationship, one
entity is related to one and only one instance of another entity.
In the One-To-One relationship, a row in Table A can have no more than one matching row
in Table B, and vice versa. A one-to-one relationship is created if both of the related
columns are primary keys or have unique constraints. In the one-to-one relationship, the
primary key acts additionally as a foreign key, and there is no separate foreign key column.
One-to-Many (1:N) Relationship:
This type of relationship is established when one entity has a collection navigation property
pointing to a reference navigation property. The related entity often has a reference
navigation property pointing back to the single entity (the “one” side). One entity instance
can be related to multiple instances of another entity but not vice versa.
The One-To-Many relationship is the most common type of relationship between database
tables or entities. In this type of relationship, a row in one table, let’s say Table A, can have
many matching rows in another table, let’s say Table B, but a row in Table B can have only
one matching row in Table A.
Many-to-Many (M:N) Relationship:
EF Core represents many-to-many relationships by introducing a joining table in the
database schema. Both entities have collection navigation properties pointing to related
entities.
In Many-to-Many Relationships, a row in one table, let’s say Table A, can have many
matching rows in another table, let’s Table B, and vice versa is also true, i.e., a row in Table
B can have many matching rows in Table A. In Relational Databases, we can create many-
to-many relationships by defining a third table, whose primary key consists of the foreign
keys from tables A and B. So, here, the Primary Key is a composite Primary key.

One-to-One Relationships in Entity Framework Core using Fluent API


In this article, I will discuss How to configure the One-to-One Relationships between two
entities in Entity Framework Core (EF Core) using Fluent API with Examples. Please
read our previous article discussing Relationships Between Entities in EF Core.
One-to-One Relationships in Entity Framework Core
In the One-To-One relationship, a row in Table A can have no more than one matching row
in Table B, and vice versa. A one-to-one relationship is created if both of the related
columns are primary keys or have unique constraints.
EF Core establishes a one-to-one relationship when both entities have a reference
navigation property pointing to the other entity. Annotations like [ForeignKey] and
[InverseProperty] are often used to define the relationship explicitly, and we have already
794

discussed these things. I will discuss Implementing One-to-One Relationships in EF Core


using Fluent API Configuration in this article.
Examples of Understanding One-to-One Relationships in EF Core
In Entity Framework Core (EF Core), we can define the one-to-one relationship between
two entities using the Fluent API Configurations. The Fluent API provides a way to configure
the relationships and mappings between two entities in a more flexible and detailed manner
than using only data annotations.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:24 / 03:1710 Sec
In Entity Framework Core (EF Core), a one-to-one relationship means that one entity can
be related to only one instance of another entity and vice versa. One-to-one relationships
can be either:
 Principal to Dependent: Here, the dependent end has the foreign key.
 Shared Primary Key: Here, the dependent uses the primary key from the
principal entity as its own primary key.
Principal to Dependent Relationship
In this scenario, one entity (Principal) is related to another entity (Dependent). The
dependent entity contains a foreign key pointing to the principal entity’s unique or primary
key. The foreign key in the Dependent Entity is applied with the unique constraint, ensuring
the one-to-one nature of the relationship.
Let us understand How to Implement One-to-One Relationships in EF Core using Fluent
API Configuration with an example. We will implement the One-to-Zero or One-to-One
relationship between the following Person and Passport entities. Each person can have only
one passport, and each passport belongs to one person.
Person.cs
So, first, create a class file with the name Person.cs and then copy and paste the following
code into it. As you can see, this is a very simple class having a few scaler properties and a
reference navigation property of the Passport entity.
namespace EFCoreCodeFirstDemo.Entities

public class Person

public int PersonId { get; set; }

public string Name { get; set; }

public Passport Passport { get; set; }

}
795

Passport.cs
Next, create another class file with the name Passport.cs and copy and paste the following
code. As you can see, this class contains a few scaler properties and a reference navigation
property pointing to the Person entity.
namespace EFCoreCodeFirstDemo.Entities

public class Passport

public int PassportId { get; set; }

public string PassportNumber { get; set; }

public int PersonId { get; set; } // Foreign Key

public Person Person { get; set; }

}
Now, we need to configure the above Person and Passport entities in such a way that EF
Core should create the Person and Passport tables in the Database and make
the PersonId column in the Person table as the Primary Key (PK) and PersonId column
in the Passport table as Foreign Key (FK) and should apply the Unique Constraints on
the PersonId Foreign Key Column.
Note: In the One-To-One or One-To-Zero relationship, a row in Table A can have a
maximum of one or zero matching rows in Table B, and vice versa is also true.
How to Configure One-to-One Relationship using Fluent API?
We can configure the One-to-One required relationship between two entities using both the
Data Annotation Attribute and Fluent API. We have already discussed configuring the One-
to-One relationship between the two entities using the ForeignKey Data Annotation
Attribute.
Now, let us proceed and try to understand how to Configure the One-to-One relationships
between two entities using Entity Framework Core Fluent API. To implement this, we must
use the following HasOne(), WithOne(), and HasForeignKey() Fluent API methods.
 HasOne(): The HasOne() method specifies the navigation property that
represents the principal end of the relationship.
 WithOne(): The WithOne() method specifies the navigation property on the
dependent side.
 HasForeignKey<TDependent>(): The HasForeignKey<TDependent>() me
thod is used to specify which property in the dependent entity represents the
foreign key.
796

Next, modify the context class as follows. The following code sets a one-to-one required
relationship between the Person and Passport entities using Entity Framework Core Fluent
API.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

// Fluent API Configuration

// Configure One to One Relationships Between Person and Passport

modelBuilder.Entity<Person>()

.HasOne(p => p.Passport) //Person has one Passport

.WithOne(p => p.Person) //Passport is associated with one Person

.HasForeignKey<Passport>(p => p.PersonId); //Foreign key in Passport table

public DbSet<Person> Persons { get; set; }

public DbSet<Passport> Passports { get; set; }


797

}
In the context class, we must configure the relationship using Fluent API within the
OnModelCreating method. The following code configures the One-to-One relationship
between the Person and Passport entities.

Let’s understand the above code step by step.


 modelBuilder.Entity<Person>() starts configuring the Person entity.
Whichever entity you want to configure using Fluent API, you need to start
configuring using the Entity method, and to this generic method, we need to
specify the entity type. In this case, it is the Person Entity.
 HasOne(p => p.Passport) method specifies that the Person entity includes
one Passport reference property using a lambda expression. It configures a
relationship where this entity type, i.e., Person entity, has a reference that
points to a single instance of the other entity, i.e., Passport in the relationship.
That means a Person has one Passport.
 WithOne(p => p.Person) configures the other end of the relationship, the
Passport entity. It specifies that the Passport entity includes a reference
navigation property of Person type. Configures this as a one-to-one
relationship. That means one Passport is associated with one Person.
 HasForeignKey<Passport>(p => p.PersonId) specifies the foreign key
property name. It configures the property, i.e., PersonId, as the foreign key for
this relationship. That means it specifies the dependent entity’s foreign key
property name.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
798

Now, if you verify the database, you should see the following.

Further, if you verify the Passport table, you will see that it applies the Unique Index on the
PersonId column, as shown in the image below. This unique index allows the column value
to be unique, and the value must be an existing PersonId, hence implementing a One-to-
One Relationship.
799

Next, modify the Main method of the Program class as follows. We have specified the
Passport reference property here while creating the Person Entity.
using EFCoreCodeFirstDemo.Entities;

using System.Net;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Creating an Instance of the Context Class

using EFCoreDbContext context = new EFCoreDbContext();

//Creating an Instance of Passport Entity

var passport = new Passport() { PassportNumber = "Pass-1224-xyz"};

//Creating Person Entity, configuring the Related Passport Entity


800

var Person = new Person() { Name = "Pranaya", Passport = passport };

context.Persons.Add(Person);

context.SaveChanges();

Console.WriteLine("Person and Passport Added");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Now, run the application and verify the database. You should see both Persons and
Passports database tables filled with one record, as shown below.

Shared Primary Key Relationship


In this case, the dependent entity shares the primary key of the principal entity. This means
that the primary key in the dependent entity is both a primary key and a foreign key to the
principal entity.
Let us consider the following User and UserProfile Entities. Each user has one profile, and
each profile belongs to one user. The profile uses the same primary key as the user.
User.cs
So, first, create a class file named User.cs and copy and paste the following code. As you
can see, this is a very simple class having a few scaler properties and a reference
navigation property of the UserProfile entity.
801

namespace EFCoreCodeFirstDemo.Entities

public class User

public int UserId { get; set; }

public string Username { get; set; }

public UserProfile UserProfile { get; set; }

}
UserProfile.cs
Next, create another class file named UserProfile.cs and copy and paste the following
code. As you can see, this class contains a few scaler properties and a reference navigation
property pointing to the User entity.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class UserProfile

[Key]

public int UserId { get; set; } // PK and FK

public string Bio { get; set; }

public User User { get; set; }

}
When the Primary Key (PK) of one table becomes the Primary Key (PK) and Foreign Key
(FK) in another table in a relational database such as SQL Server, MySQL, Oracle, etc.,
802

then it is said to be in a One-to-One or One-to-Zero relationship between the two database


tables.
So, we need to configure the above User and UserProfile entities in such a way that EF
Core should create the User and UserProfile tables in the Database and make the UserId
column in the User table as the Primary Key (PK) and UserId column in the UserProfile
table as Primary Key (PK) and Foreign Key (FK).
Next, modify the context class as follows. The following code sets a one-to-one required
relationship between the User and UserProfile entities using Entity Framework Core Fluent
API.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

// Fluent API Configuration

// Configure One to One Relationships User Person and UserProfile

modelBuilder.Entity<User>()

.HasOne(u => u.UserProfile) //Person has one UserProfile

.WithOne(up => up.User) //One UserProfile is associated with one User

.HasForeignKey<UserProfile>(up => up.UserId); //Foreign key in UserProfile table


803

public DbSet<User> Users { get; set; }

public DbSet<UserProfile> UserProfiles { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following.
804

Next, modify the Main method of the Program class as follows. While creating the User
Entity, we have specified the UserProfile reference property.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

{
805

//Creating an Instance of the Context Class

using EFCoreDbContext context = new EFCoreDbContext();

//Creating an Instance of UserProfile Entity

var userProfile = new UserProfile() { Bio = "Software Developer...."};

//Creating User Entity, configuring the Related UserProfile Entity

var user = new User() { Username = "Pranaya", UserProfile = userProfile };

context.Users.Add(user);

context.SaveChanges();

Console.WriteLine("User and UserProfile Added");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
You can also start configuring with the UserProfile entity in the same way, as follows.

So, modify the context class as follows:


using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities
806

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

// Fluent API Configuration

// Configure One to One Relationships User Person and UserProfile

modelBuilder.Entity<UserProfile>()

.HasOne(u => u.User) //UserProfile has one User

.WithOne(up => up.UserProfile) //One User is associated with one UserProfile

.HasForeignKey<User>(up => up.UserId); //Foreign key in User table

//modelBuilder.Entity<User>()

// .HasOne(u => u.UserProfile) //Person has one UserProfile

// .WithOne(up => up.User) //One UserProfile is associated with one User

// .HasForeignKey<UserProfile>(up => up.UserId); //Foreign key in UserProfile table

public DbSet<User> Users { get; set; }


807

public DbSet<UserProfile> UserProfiles { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes in place, open the Package Manager Console and Execute
the add-migration and update-database commands as follows. You can give any name to
your migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should
not be given earlier.

Now, if you verify the database, you should see the following.
808

The choice between principal-dependent and shared primary key relationships should be
based on the domain model and specific requirements of your application. The principal-
dependent relationship is more common in real-world scenarios.
Note: By using the Fluent API, we have more control over the relationship configuration,
such as customizing the foreign key property names, specifying cascading delete behavior,
and other advanced features. Remember that the Fluent API configuration takes
precedence over data annotations when both are used together.

One-to-Many Relationships in Entity Framework Core using Fluent API


In this article, I will discuss How to Configure One-to-Many Relationships between two
Entities in Entity Framework Core using Fluent API with Examples. Please read our
previous article, which discusses How to Configure One-to-One Relationships between
two entities in EF Core using Fluent API.
One-to-Many Relationships in Entity Framework Core using Fluent API
The One-To-Many relationship is the most common type of relationship between database
tables or entities. In this type of relationship, a row in one table, let’s say Table A, can have
many matching rows in another table, let’s say Table B, but a row in Table B can have only
one matching row in Table A.
809

In Entity Framework Core (EF Core), a one-to-many (1:N) relationship means that one
entity (often termed the “principal” or “parent”) can be related to multiple instances of
another entity (often termed the “dependent” or “child”), but each instance of the dependent
entity relates back to only one instance of the principal entity.
For a one-to-many relationship, the principal entity typically has a collection navigation
property, while the dependent entity will have a reference navigation property and a foreign
key property. To represent a one-to-many relationship, we will typically have:

Enchanted by the Beautiful City near Cambodia Border - Nếm TV00:17 / 02:4810 Sec
 A collection navigation property in the principal entity points to a collection of
dependent entities.
 A foreign key property in the dependent entity pointing back to the principal
entity.
 A reference navigation property in the dependent entity pointing back to the
principal entity.
Examples of Understanding One-to-Many Relationships in EF Core
In Entity Framework Core (EF Core), you can define a one-to-many relationship between
entities using the Fluent API. The Fluent API provides a way to configure the relationships
and mappings between entities in a more flexible and detailed manner than using only data
annotations.
Let us understand this with an example. We will implement a One-to-Many relationship
between the following Author and Book Entities. Consider the following Author and Book
classes where the Author entity includes many Book entities, but each book entity is
associated with only one Author entity. So, an Author can write multiple Books, but each
Book has only one Author.
Author.cs
First, create a class file named Author.cs, and copy and paste the following code. As you
can see, this entity has a few scaler properties and one collection navigation property
pointing to the Book entity. This will be our Principal Entity.
namespace EFCoreCodeFirstDemo.Entities

public class Author

public int AuthorId { get; set; }

public string? Name { get; set; }

// Navigation property: Collection pointing to dependent entities

public List<Book>? Books { get; set; }

}
810

}
Book.cs
Next, create another class file named Book.cs, and copy and paste the following code. As
you can see, this entity has a few scaler properties and one reference navigation property
pointing to the Author entity. AuthorId is going to be the Foreign Key. This will be our
Dependent Entity.
namespace EFCoreCodeFirstDemo.Entities

public class Book

public int BookId { get; set; }

public string? Title { get; set; }

// Foreign key property

public int AuthorId { get; set; }

// Navigation property: Points back to the principal entity

public Author? Author { get; set; }

}
How to Configure One-to-Many Relationships Using Fluent API in EF
Core?
Generally, we do not need to configure the one-to-many relationship in the entity framework
core because one-to-many relationship conventions cover all combinations. However, you
may configure relationships using Fluent API in one place to make them more maintainable.
Now, you can configure the one-to-many relationship for the above two entities in Entity
Framework Core using Fluent API by overriding the OnModelCreating method in the context
class. To do so, we will use the following Fluent API methods.
1. HasMany(): Configures a relationship where this entity type has a collection
that contains instances of the other type in the relationship. That means it
specifies the ‘many’ side of the relationship.
2. WithOne(): Configures a relationship where this entity type has a reference
that contains an instance of the other type in the relationship. That means it
specifies the ‘one’ side of the relationship.
3. HasForeignKey(): Configures the property(s) to use as the foreign key for
this relationship. That means it specifies which property is the foreign key.
811

Now, modify the context class as follows. In this example, the Author is the Principal Entity,
and the Book is the dependent Entity. One Author can have multiple books, and each book
can belong to a single author. So, in the Book Entity, we will create the Foreign Key.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

// Fluent API Configuration

// Configure One to Many Relationships Between Author and Book

modelBuilder.Entity<Author>()

.HasMany(a => a.Books) // Author has many Books, specifies the 'many' side of the
relationship

.WithOne(b => b.Author) // Book is associated with one Author, specifies the 'one' side of the
relationship

.HasForeignKey(b => b.AuthorId); // AuthorId is the Foreign key in Book table, specifies
which property is the foreign key

}
812

public DbSet<Author> Authors { get; set; }

public DbSet<Book> Books { get; set; }

}
In the context class, we need to configure the relationship using EF Core Fluent API within
the OnModelCreating method. The following code configures the One-to-Many relationship
between the Author and Book entities. That means one Author can have multiple books (0
or 1, n number of books), and one book can belong to a single Author.

Code Explanation:
 modelBuilder.Entity<Author>(): First, we must start configuring with one
entity class, either Author or Book. So, here, we start configuring the Author
entity.
 HasMany(a => a.Books): Specifies the ‘many’ side of the relationship. This
method specifies that the Author entity includes one Books collection
navigation property using a lambda expression. It configures a relationship
where this entity type, i.e., Author entity, has a collection property, i.e., Books,
that points to instances of the other entity, i.e., Book, in the relationship. That
means the Author has many Books.
 WithOne(b => b.Author): Specifies the ‘one’ side of the relationship. This
method configures the other end of the relationship, the Book. It specifies
that the Book entity includes a reference navigation property of Author type,
i.e., Book entity, has a reference property, i.e., Author, that points to an
instance of the other entity, i.e., Author, in the relationship. That means the
one Book is associated with only one Author.
 HasForeignKey(b => b.AuthorId): Specifies which property is the foreign
key. This method specifies the foreign key property name. It configures the
property, i.e., AuthorId, as the foreign key for this relationship. That means
AuthorId is the Foreign key in the Book table.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
813

migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following.

Next, modify the Main method of the Program class as follows:


using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo
814

internal class Program

static void Main(string[] args)

try

using var context = new EFCoreDbContext();

//First Create Book Collection

var books = new List<Book>

new Book() { Title = "C#" },

new Book() { Title = "LINQ" },

new Book() { Title = "ASP.NET Core" }

};

//Create an Instance of Author and Assign the books collection

var author = new Author() { Name="Pranaya", Books = books };

context.Authors.Add(author);

context.SaveChanges();

Console.WriteLine("Author and Books Added");

Console.ReadKey();

catch (Exception ex)

{
815

Console.WriteLine($"Error: {ex.Message}"); ;

}
Execute the above code and then verify the database. You will see one Author record with
three corresponding books, as shown in the image below.

How to Configure Cascade Delete using EF Core Fluent API?


In Entity Framework Core (EF Core), the OnDelete method configures delete behavior for
related entities when the principal entity is deleted. This behavior is often described in terms
of cascading actions.
When defining relationships between entities, especially with foreign keys, it’s important to
determine what should happen when a record referenced by another record is deleted. The
OnDelete method allows you to specify this behavior.
Cascade Delete means automatically deleting the child rows when the related parent row is
deleted. For example, if the Author is deleted from the Authors table, then automatically, all
the books belonging to that Author should also be deleted from the Books table. To
implement this, we need to use the OnDelete Fluent API Method. So, modify the
EFCoreDbContext class as follows. Here, you can see we are using the OnDelete method,
and to this method, we are passing DeleteBehavior.Cascade enum named constant.
using Microsoft.EntityFrameworkCore;
816

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

// Fluent API Configuration

// Configure One to Many Relationships Between Author and Book

modelBuilder.Entity<Author>()

.HasMany(a => a.Books) // Author has many Books, specifies the 'many' side of the
relationship

.WithOne(b => b.Author) // Book is associated with one Author, specifies the 'one' side of the
relationship

.HasForeignKey(b => b.AuthorId) // AuthorId is the Foreign key in Book table, specifies
which property is the foreign key

.OnDelete(DeleteBehavior.Cascade); //This will delete the child record(S) when parent


record is deleted

}
817

public DbSet<Author> Authors { get; set; }

public DbSet<Book> Books { get; set; }

}
The OnDelete() of the Fluent API method uses the DeleteBehavior parameter. If you go to
the definition of DeleteBehavior, you will see it is an enum with the following values.
DeleteBehavior Indicates how a delete operation is applied to dependent entities in a
relationship when the principal is deleted, or the relationship is severed.

You can specify any of the above DeleteBehavior values based on your requirements. The
meaning of the above enum named constants are as follows:
1. Cascade: For entities being tracked by the context, dependent entities will be
deleted when the related principal is deleted.
2. ClientSetNull: For entities being tracked by the context, the values of foreign
key properties in dependent entities are set to null when the related principal
is deleted. If a property cannot be set to null because it is not a nullable type,
an exception will be thrown when SaveChanges() is called.
3. Restrict: For entities being tracked by the context, it prevents the principal
entity from being deleted if any related dependents exist. This is useful to
prevent accidental data loss.
4. SetNull: For entities being tracked by the context, the values of foreign key
properties in dependent entities are set to null when the related principal is
deleted. If a property cannot be set to null because it is not a nullable type, an
exception will be thrown when SaveChanges() is called.
818

5. ClientCascade: For entities being tracked by the context, dependent entities


will be deleted when the related principal is deleted.
6. NoAction: For entities being tracked by the context, the values of foreign key
properties in dependent entities are set to null when the related principal is
deleted. If a property cannot be set to null because it is not a nullable type, an
exception will be thrown when SaveChanges() is called.
7. ClientNoAction: It is unusual to use this value. Consider using
DeleteBehavior.ClientSetNull instead to match the behavior of EF6 with
cascading deletes disabled.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig3. The name that you are giving it should not be
given earlier.

Currently, we have one author in the Authors database table with the AuthorId 1, and it has
three corresponding records in the Books database table, as shown in the image below.
819

Modifying the Main Method:


Now, let us modify the Main method as follows. Here, we are deleting the Principal Author
Entity whose ID is 1. And then, we will verify whether the corresponding Book entities are
going to be deleted or not.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using var context = new EFCoreDbContext();

Author? Id1 = context.Authors.Find(1);


820

if (Id1 != null)

context.Remove(Id1);

context.SaveChanges();

Console.WriteLine("Author Deleted");

else

Console.WriteLine("Author Not Exists");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Once you execute the application code, verify the database and check the Books table, and
you will see the corresponding Author records should have been deleted.
Points to Remember:
1. Database Provider Limitations: Not all delete behaviors are supported by
all database providers. It’s essential to check the capabilities of your database
provider when choosing a delete behavior.
2. Data Integrity: Cascading deletes can lead to unintentional data loss. Always
ensure you understand the implications of your chosen delete behavior,
especially in production scenarios.
821

3. Database-Level vs. Application-Level: It’s important to note that while EF


Core can handle cascading actions at the application level, it’s often
recommended to have these actions defined at the database level for
performance and data integrity. When you use OnDelete, EF Core will try to
configure the behavior at the database level, but it’s essential to know how
your database handles these actions.
How to Implement Update Behavior to Dependent Entities in EF Core
using Fluent API?
In Entity Framework Core (EF Core), when dealing with related entities, you often need to
determine the behavior for updates, especially when a principal entity’s primary key (often
used as a foreign key in the dependent entity) changes. This is less common than the
delete behavior scenarios, mainly because primary keys are typically stable and don’t
change often. However, it’s important to understand how updates work in relationships,
particularly for non-primary key properties.
For Relationships in EF Core:
 Non-Primary Key Property Updates: By default, when you modify
properties of a principal entity that are not part of the primary key, and those
properties are involved in a relationship (e.g., used as a foreign key in a
dependent entity), the changes won’t cascade to the dependent entities
automatically. You will need to handle such updates manually in your
application logic.
 Primary Key Property Updates: Changing primary keys is generally not
suggested because a primary key’s purpose is to serve as a unique identifier.
If you need to change primary keys, it often indicates a design issue in the
schema. EF Core does not support changing the value of primary keys for
tracked entities out of the box. In such cases, you need to handle this with
custom logic, such as deleting the old record and creating a new one, then
updating any dependents manually.

Many-to-Many Relationships in Entity Framework Core using Fluent API


In this article, I will discuss How to Configure Many-to-Many Relationships between two
Entities in Entity Framework Core using Fluent API with Examples. Please read our
previous article, which discusses How to Configure One-to-Many Relationships in EF
Core using Fluent API.
Many-to-Many Relationships in Entity Framework Core
In many-to-many relationships, a row in one table, let’s say TableA, can have many
matching rows in another table, let’s say TableB and vice versa is also true, i.e., a row in
TableB can have many matching rows in TableA. In Relational Databases, we can create
many-to-many relationships by defining a third table, whose primary key consists of the
foreign keys from tableA and tableB. So, here, the Primary Key is a composite Primary key.
In Entity Framework Core 5.0 and later versions, support for many-to-many relationships
was reintroduced, allowing for simpler configurations without explicitly defining the joining
822

table/entity in our model. This simplification makes it easier to model and work with many-
to-many relationships.
Examples of Understanding Many-to-Many Relationships in EF Core
Using Fluent API
In Entity Framework Core (EF Core), we can define a many-to-many relationship between
entities using the Fluent API. Let’s configure a many-to-many relationship using the Fluent
API in EF Core. Suppose we have two entities, Student and Course, where each student
can enroll in multiple courses, and each course can have multiple students. So, let us
create the following two entities.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
Student.cs
First, create a class file named Student.cs and copy and paste the following code. Here,
you can see we have a collection navigation property that is pointing to the Course entity.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string? Name { get; set; }

// Navigation property for the many-to-many relationship

//One Student Can have Many Courses

public List<Course> Courses { get; set; }

}
Course.cs
Next, create another class file with the name Course.cs and then copy and paste the
following code into it. Here, you can see we have a collection navigation property that is
pointing to the Student entity.
namespace EFCoreCodeFirstDemo.Entities

public class Course


823

public int CourseId { get; set; }

public string? CourseName { get; set; }

public string? Description { get; set; }

//Navigation property for the many-to-many relationship

//One Course Can be Taken by Many Students

public List<Student> Students { get; set; }

}
EF Core will automatically infer the many-to-many relationship with these entities due to the
presence of navigation properties. However, if you want to configure the relationship
explicitly using Fluent API, you can do so. So, modify the EFCoreDbContext class as
follows to configure Many to Many Relationships using Fluent API. In this configuration, we
use the HasMany, WithMany, and UsingEntity methods to define the many-to-many
relationship between Student and Course.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

}
824

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<Student>()

.HasMany(s => s.Courses) // Student can enroll in many Courses

.WithMany(c => c.Students) // Course can have many Students

.UsingEntity(j => j.ToTable("StudentCourses")); //Explicitly set the join table name

public DbSet<Student> Students { get; set; }

public DbSet<Course> Courses { get; set; }

}
In this Fluent API configuration:
 HasMany(s => s.Courses): Specifies that a Student can have many
Courses.
 WithMany(c => c.Students): Specifies that a Course can have many
Students.
 UsingEntity(j => j.ToTable(“StudentCourses”)): Explicitly names the join
table. If you don’t specify this, EF Core will use a default name based on the
two entity names.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
825

Now, if you verify the database, you should see the following.

Next, modify the Main method of the Program class as follows:


using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program


826

static void Main(string[] args)

try

//Creating an Instance of Context Class

using var context = new EFCoreDbContext();

//Creating List of Courses

var courses = new List<Course>

new Course() { CourseName = "EF Core", Description="It is an ORM Tool"},

new Course() { CourseName = "ASP.NET Core", Description ="Open-Source & Cross-


Platform" }

};

context.Courses.AddRange(courses);

context.SaveChanges();

Console.WriteLine("Courses Added");

//Creating an Instance of Student Class

var Pranaya = new Student() { Name = "Pranaya", Courses = courses };

context.Students.Add(Pranaya);

var Hina = new Student() { Name = "Hina", Courses = courses };

context.Students.Add(Hina);

context.SaveChanges();

Console.WriteLine("Students Added");
827

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Now, run the above code and verify the StudentCourses table, and you should see the
following.

Before EF Core 5.0


Suppose you are working with EF Core 4.0 and earlier versions. In that case, you need to
configure the Many to Many relationship as follows: Here, we need to explicitly define the
join table/entity in your model.
Student.cs
First, modify the Student class as follows. Here, you can see we have a collection
navigation property that is pointing to the Course entity. Also, we have the StudentCourses
property, which is required for Implementing Many-to-Many Relationships.
namespace EFCoreCodeFirstDemo.Entities

public class Student

{
828

public int StudentId { get; set; }

public string? Name { get; set; }

//One Student Can have Many Courses

public List<Course> Courses { get; set; } = new();

//StudentCourses Collection Property for Implementing Many to Many Relationship

public List<StudentCourse> StudentCourses { get; set; }

}
Course.cs
Next, modify the Course class as follows. Here, you can see we have a collection
navigation property that is pointing to the Student entity. Also, we have the StudentCourses
property, which is required for Implementing Many-to-Many Relationships.
namespace EFCoreCodeFirstDemo.Entities

public class Course

public int CourseId { get; set; }

public string? CourseName { get; set; }

public string? Description { get; set; }

//One Course Can be Taken by Many Students

public List<Student> Students { get; set; } = new();

//StudentCourses Collection Property for Implementing Many to Many Relationship

public List<StudentCourse> StudentCourses { get; set; }

}
829

Next, define a new joining entity class that includes both entities’ foreign key and reference
navigation properties. So, create a class file named StudentCourse.cs and copy and paste
the following code. This entity has two foreign key properties (StudentId and CourseId) and
two reference navigation properties pointing to the corresponding entities.
namespace EFCoreCodeFirstDemo.Entities

public class StudentCourse

public int StudentId { get; set; }

public Student Student { get; set; } = null!;

public int CourseId { get; set; }

public Course Course { get; set; } = null!;

}
The foreign keys must be the composite primary key in the joining table. This can only be
configured using Fluent API. So, modify the context class as follows to configure both the
foreign keys in the joining entity as a composite key using Fluent API. In this configuration,
we use the HasMany, WithMany, and UsingEntity methods to define the many-to-many
relationship between Student and Course.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String


830

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<Student>()

.HasMany(e => e.Courses) // Student can enroll in many Courses

.WithMany(e => e.Students) // Course can have many Students

// Define the joining table

.UsingEntity<StudentCourse>(

//navigations needs to be configured explicitly

l => l.HasOne<Course>(e => e.Course).WithMany(e => e.StudentCourses), //MapLeftKey

r => r.HasOne<Student>(e => e.Student).WithMany(e => e.StudentCourses));


//MapRightKey

public DbSet<Student> Students { get; set; }

public DbSet<Course> Courses { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
831

Now, if you verify the database, you should see the following.

Next, modify the Main method of the Program class as follows:


using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)


832

try

//Creating an Instance of Context Class

using var context = new EFCoreDbContext();

//Creating List of Courses

var courses = new List<Course>

new Course() { CourseName = "EF Core", Description="It is an ORM Tool"},

new Course() { CourseName = "ASP.NET Core", Description ="Open-Source & Cross-


Platform" }

};

context.Courses.AddRange(courses);

context.SaveChanges();

Console.WriteLine("Courses Added");

//Creating an Instance of Student Class

var Pranaya = new Student() { Name = "Pranaya", Courses = courses };

context.Students.Add(Pranaya);

var Hina = new Student() { Name = "Hina", Courses = courses };

context.Students.Add(Hina);

context.SaveChanges();

Console.WriteLine("Students Added");

Console.ReadKey();

}
833

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

If you check the StudentCourse database table, you’ll find this data.

With EF Core 5.0 and later, setting up many-to-many relationships has become more easier
and less error-prone. The ability to implicitly handle the join table or further configure it using
the Fluent API provides developers with flexibility while maintaining clarity in their data
models.

Self-Referencing Relationship in Entity Framework Core


In this article, I will discuss How to Configure Self-Referencing Relationships in Entity
Framework Core using Fluent API with Examples. Please read our previous article, which
discusses How to Configure Many-to-Many Relationships in EF Core using Fluent API.
Self-Referencing Relationship in Entity Framework Core
A self-referencing relationship in Entity Framework Core (EF Core) occurs when an entity
has a relationship with instances of the same entity type. This is useful for scenarios where
an entity is related to other entities of the same type, such as in hierarchical structures like
organizational charts or comments on a post.
A self-referencing relationship, also known as a recursive relationship, occurs when an
entity is related to itself. An example scenario is in an organizational structure where an
Employee can have a manager who is also an Employee.
834

Similar to a self-referencing relationship in a relational database, an entity can have a


relationship with instances of the same entity type. For example, an Employee entity can
have a Manager navigation property that points to another Employee. Let’s say we have an
entity called Employee that has a self-referencing relationship to represent a manager-
subordinate relationship:

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:07 / 03:1710 Sec

namespace EFCoreCodeFirstDemo.Entities

public class Employee

public int EmployeeId { get; set; }

public string? Name { get; set; }

// ForeignKey for Manager

public int? ManagerId { get; set; }

// Navigation property for Manager

public Employee? Manager { get; set; }

// Navigation property for Subordinates

public List<Employee>? Subordinates { get; set; }

}
In the above example, an Employee entity has a Manager navigation property that points to
another Employee, representing the manager of the current employee representing the one-
to-one relationship. Additionally, it has a collection navigation property called Subordinates
to represent the employees who report to the current employee, representing the one-to-
many relationships. In this model:
 ManagerId is a foreign key pointing to another Employee (the manager).
 Manager is a reference navigation property pointing to the manager.
 Subordinates is a collection navigation property containing all Subordinates
(employees) under the current Employee.
835

To configure the self-referencing relationship, we need to use Fluent API in the


OnModelCreating method of our DbContext class. So, modify the EFCoreDbContext class
as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configuring the Employee Entity to Represent Self-Referential Relationships

modelBuilder.Entity<Employee>()

.HasOne(e => e.Manager) // Specifies that each employee has one manager.

.WithMany(e => e.Subordinates) // Specifies that each manager can have many Subordinates

.HasForeignKey(e => e.ManagerId) // Specifies the foreign key property for this relationship.

.OnDelete(DeleteBehavior.Restrict); // Optional: Configure delete behavior

public DbSet<Employee> Employees { get; set; }

}
836

}
In this configuration, we use the HasOne and WithMany methods to define the self-
referencing relationship between Employee and himself. The HasForeignKey method
specifies the foreign key property (ManagerId) in the Employee table. You can also specify
the delete behavior using the OnDelete method, which determines what happens to related
entities when an entity is deleted. In this Fluent API configuration:
 HasOne(e => e.Manager): Specifies that each Employee has one Manager
(who is also an Employee).
 WithMany(m => m.Subordinates): Specifies that an Employee (as a
manager) can have many Subordinates.
 HasForeignKey(e => e.ManagerId): Specifies the foreign key property for
the relationship.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following.
837

Next, modify the Main method of the Program class as follows.


using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

using var context = new EFCoreDbContext();

//Create a Manager

Employee mgr = new Employee() { Name = "Pranaya"};

context.Add(mgr);

context.SaveChanges();

Console.WriteLine("Manager Added");
838

Employee emp1 = new Employee() { Name ="Hina", ManagerId = 1};

context.Add(emp1);

Employee emp2 = new Employee() { Name ="Ramesh", ManagerId = 1 };

context.Add(emp2);

context.SaveChanges();

Console.WriteLine("Employees Added");

//List of Employee including Manager

var listOfEmployees = context.Employees.ToList();

Console.WriteLine("List of Employees");

foreach (var emp in listOfEmployees)

Console.WriteLine($"\tEmployee ID:{emp.Name}, Name:{emp.Name}");

//List of Subordinates working under Manager whose Id = 1

var Manager = context.Employees.Find(1);

if( Manager != null )

Console.WriteLine($"\nList of Employees working under Manager : {Manager.Name}");

if (Manager.Subordinates != null)

foreach (var emp in Manager.Subordinates)

Console.WriteLine($"Employee ID:{emp.Name}, Name:{emp.Name}");

}
839

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Entity Configurations using Entity Framework (EF) Core Fluent API


In this article, I will discuss Entity Configurations using Entity Framework Core (EF
Core) Fluent API with Examples. Please read our previous article discussing How to
Configure Fluent API in Entity Framework Core with Examples. At the end of this article,
you will understand the following pointers:
1. Entity Configurations using Entity Framework Core Fluent API
840

2. How to Configure Default Schema using Entity Framework Core Fluent


API?
3. How to Map Entity to Table using Fluent API in Entity Framework Core?
4. Examples to Understand HasAlternateKey(), HasIndex(), HasKey(), and
Ignore() Fluent API Methods.
5. How to Create a Table without Primary Key using EF Core Fluent API?
Entity Configurations using Entity Framework Core Fluent API
Entity Framework Core’s Fluent API allows us to configure the mapping of our entity classes
to database tables and define various aspects of their behavior. We can use the Fluent API
to configure properties, relationships, keys, indexes, and more. Here, I am going to discuss
how to use the Fluent API to configure entity classes in Entity Framework Core:
Example to Understand Entity Configurations using EF Core Fluent API:
We will use the following Student and Standard Entities to understand Entity Configurations
in Entity Framework Core Code First Approach using Fluent API.
Student.cs
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string? LastName { get; set; }

public string Branch { get; set; }

public string City { get; set; }

public string State { get; set; }

public string Country { get; set; }

public Standard Standard { get; set; }

}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities
841

public class Standard

public int StandardId { get; set; }

public string StandardName { get; set; }

public string Description { get; set; }

public ICollection<Student> Students { get; set; }

}
Configure Default Schema using Entity Framework Core Fluent API
Configuration:
Let us understand how to configure the default schema for the tables in the database.
However, changing the schema while creating the individual tables is also possible. By
default, the schema is created as dbo. For a better understanding, please modify the
context class as follows.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:21 / 03:1710 Sec

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String


842

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following.
843

Now, what our requirement is, instead of dbo, we want to set the default schema as Admin.
To do so, we need to use the HasDefaultSchema method, and to this method, we need to
pass the default schema name.
So, to configure a default schema for all tables in Entity Framework Core using the Fluent
API, we need to use the ModelBuilder’s HasDefaultSchema method within the
OnModelCreating method of our DbContext class. So, modify the Context class as follows.
In the below code, we have set Admin as a default schema.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

// Set default schema


844

modelBuilder.HasDefaultSchema("Admin");

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig2. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following. You will see that both the
tables will be created with Admin schema, as shown in the below image.

By setting the default schema using HasDefaultSchema, every entity will be created under
this schema unless specifically overridden using the ToTable method with the schema
845

parameter. Later in this article, I will explain how to specify a different schema using the
ToTable method.
Map Entity to Table using Fluent API in Entity Framework Core.
By default, the Entity Framework Core Code-First Approach will create the tables in the
database with the name DbSet properties in the context class. For example, modify the
context class as follows. Here, we have included two DbSet Properties named Students and
Standards.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
In this case, the Entity Framework Core will create two tables in the database with the
name Students and Standards as the DbSet Properties name are Students and
Standards. We can override this default convention using Data Annotation Attribute and
846

Fluent API in EF Core. We have already discussed using the Table Data Annotation
Attribute with Entity Framework Core. Let us understand how to override the default
convention using Entity Framework Core Fluent API.
How to Map Entity to Table using Fluent API in Entity Framework Core?
To Map an Entity to a Table using Entity Framework Fluent API, we must use the ToTable
method. The ToTable() method is used to specify the name of the database table
associated with the entity. Useful when the table name does not match the default naming
convention
There are two overloaded versions available of this method. One overloaded version takes
the tableName as a parameter, while the other method takes tableName and database
schemaName as parameters.
So, modify the context class to give a table name different than the DbSet properties name.
Here, we need to override the onModelCreating method. Even though the DbSet property
names are Students and Standards, the Entity Framework Core will generate tables with
the names StudentInfo and StandardInfo in the database. The point that you need to
remember is that modelBuilder.Entity<TEntity>() returns
the EntityTypeConfiguration object, using which we can configure many things.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Map Entity to Table

modelBuilder.Entity<Student>().ToTable("StudentInfo"); //Default Schema will be dbo


847

modelBuilder.Entity<Standard>().ToTable("StandardInfo", "Admin"); // Schema will be


Admin

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
As you can see in the above code, we start with the Entity<TEntity>() method. Most of the
time, we have to start with the Entity<TEntity>() method to configure the Entity using
Fluent API, and here, we need to provide the Entity name for which we want to provide
configuration. Then, we used the ToTable() method to map the Student entity to
the StudentInfo table and the Standard entity to the StandardInfo table. Here, the
StandardInfo table will be created with the Admin schema because we have specified the
Admin schema for the StandardInfo table. Studentinfo table will be created with the default
schema as we have not specified schema, and in this case, the default schema will be dbo.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig3. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following. Here, you can see the
StudentInfo table is created with the default dbo schema, and the StandardInfo table is
created with the Admin schema.
848

HasAlternateKey(), HasIndex(), HasKey() and Ignore() Fluent API


methods examples using EF Core
In Entity Framework Core, you can use various Fluent API methods to configure the
database schema and entity mappings. Here are examples of how to use HasAlternateKey,
HasIndex, HasKey, and Ignore methods in the Fluent API:
HasAlternateKey() Method:
The HasAlternateKey method is used to specify an alternate key for an entity. Alternate
keys are unique constraints that allow us to enforce uniqueness on a set of columns other
than the primary key.
modelBuilder.Entity<Employee>()

.HasAlternateKey(p => p.Email); // Email is an alternate key


HasIndex() Method:
The HasIndex method creates an index on one or more columns of an entity to improve
query performance.
modelBuilder.Entity<Employee>()

.HasIndex(p => p.RegdNumber) // Create an index on the RegdNumber column

.IsUnique(); // Make the index unique


HasKey() Method:
The HasKey method is used to specify the primary key of an entity. You can configure a
composite key by passing multiple properties to the method.
Single-column primary key

modelBuilder.Entity<Employee>()

.HasKey(p => p.EmpId);

Composite key example


849

modelBuilder.Entity<Order>()

.HasKey(o => new { o.EmpId, o.RegdNumber });


Ignore() Method:
The Ignore method excludes a property from being mapped to the database. This is useful
when you have properties in your entity class that should not be persisted in the database.
It also configures that the class should not be mapped to a table.
modelBuilder.Entity<Person>()

.Ignore(p => p.TemporaryAddress); // Exclude TemporaryAddress property from mapping


Let us understand the above Fluent API Methods with an example.
Creating the Employee Model:
Create a class file named Employee.cs, then copy and paste the following code.
namespace EFCoreCodeFirstDemo.Entities

public class Employee

public int EmpId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Email { get; set; }

public string RegdNumber { get; set; }

public string TemporaryAddress { get; set; }

}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext


850

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Email is an Alternate Key, Applying Unique Constraints

modelBuilder.Entity<Employee>()

.HasAlternateKey(p => p.Email);

//Create an index on the RegdNumber column

modelBuilder.Entity<Employee>()

.HasIndex(p => p.RegdNumber)

.IsUnique(); //Make the index unique and by default non-clustered

//Single-Column Primary Key

modelBuilder.Entity<Employee>()

.HasKey(p => p.EmpId);

//Exclude TemporaryAddress property from mapping

modelBuilder.Entity<Employee>()

.Ignore(p => p.TemporaryAddress);

}
851

public DbSet<Employee> Employees { get; set; }

}
In this example, we configure the Employee entity with an alternate key or Unique Key on
the Email property, a unique index on the RegdNumber property, a primary key on the
EmpId property, and exclude the TemporaryAddress property from mapping to the
database.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig3. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following.
852

How to Create a Table without Primary Key using EF Core Fluent API?
In Entity Framework (EF) Core, it’s a common practice to define a primary key for each
table, as primary keys uniquely identify rows in a table. However, there are scenarios where
you might want to create a table without a primary key, such as mapping to an existing
database table or dealing with a non-standard database schema. You can achieve this
using EF Core’s Fluent API by configuring the entity as a keyless entity.
Let us see how we can create a table without a primary key using EF Core Fluent API.
Define the entity class without a primary key. Create a class file with the name
TableWithOutPrimary key. The following entity will represent the table without a primary
key.
namespace EFCoreCodeFirstDemo.Entities

public class TableWithoutPrimaryKey

public string Name { get; set; }

public string Email { get; set; }

public string Address { get; set; }


853

// Other properties...

}
Next, modify the context class as follows. In our DbContext class, we need to override the
OnModelCreating method and use the Fluent API to configure the entity as a keyless entity
using the HasNoKey method:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<TableWithoutPrimaryKey>()

.HasNoKey(); // Configure the entity as a keyless entity

public DbSet<TableWithoutPrimaryKey> TablesWithoutPrimaryKey { get; set; }

}
854

With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig4. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the following.

Primary Key and Composite Primary Key using Entity Framework Core Fluent
API
In this article, I will discuss Configuring Primary Key and Composite Primary Key using
Entity Framework Core (EF Core) Fluent API with Examples. Please read our previous
article discussing Entity Configurations using Entity Framework Core Fluent API with
Examples. At the end of this article, you will understand the following pointers.
855

1. Property Configurations using Entity Framework Core Fluent API


2. Configuring a Single Primary Key using EF Core Fluent API
3. Switching off Identity for Numeric Primary Key using EF Core Fluent API
4. Configuring a Composite Primary Key using EF Core Fluent API
5. Creating Primary Key on String Property using EF Core Fluent API
Property Configurations using Entity Framework Core Fluent API
In Entity Framework Core (EF Core), we can also use the Fluent API to configure various
aspects of the properties of an entity to map it with the database column in the database.
Using EF Core Fluent API, we can change the corresponding column name, data type, and
size. We can also configure whether the column will be Null or NotNull. It is also possible to
configure the PrimaryKey, ForeignKey, concurrency column, etc., in the database using
Entity Framework Core Fluent API Property Configurations.
Configuring Primary Key and Composite Primary Key using EF Core
Fluent API
In Entity Framework (EF) Core, you can configure primary keys for your entities using the
Fluent API. You can define a single primary key or a composite primary key (a primary key
consisting of multiple columns). To understand How to Configure the Primary Key and
Composite Primary Key in Entity Framework Core using Fluent API, we will use the
following Student Entity.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentRegdNumber { get; set; }

public int StudentRollNumber { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

}
Let us understand how to Configure Primary Key and Composite Primary Key using Entity
Framework Core Fluent API with an example. If you see our Student domain class, it does
not have either Id or <Entity Name>+ Id property. The Entity Framework Core will not follow
the default code-first conventions while generating the database tables. So, in situations like
this, we can go with Entity Framework Core Fluent API and configure the key property using
the HasKey() method.
Ad
856

1/2
00:48

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV

Configuring a Single Primary Key using EF Core Fluent API:


To configure a single primary key for an entity, we need to use the HasKey method in
the OnModelCreating method of our DbContext class. For a better understanding, please
modify the context class as follows. Here, we have marked
the StudentRegdNumber property of the Student Entity as the Primary key using
the HasKey EF Core Fluent API method. When the Primary key (Not Composite Primary
Key) is created on integer data type, it will be created as an Identity column in the database.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configure Primary Key using HasKey method

modelBuilder.Entity<Student>().HasKey(s => s.StudentRegdNumber);

public DbSet<Student> Students { get; set; }


857

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database and check the Primary Key column, you can see
StudentRegdNumber will be created as the Primary key, as shown in the below image.

Switching off Identity for Numeric Primary Key using EF Core Fluent
API:
In Entity Framework Core, we can configure a numeric primary key column not to use the
identity/auto-increment feature. The Identity column will be generated for the Integer Type
key only. If we want to provide the values for the Primary Key column explicitly, we need to
858

switch off the identity. To do this using the Fluent API, we need to specify that the column
should not be generated as an identity column. We can achieve this by using
the ValueGeneratedNever method.
In our DbContext class, we must use the Fluent API in the OnModelCreating method to
configure the primary key column. So, modify the context class as follows. Here, we specify
the primary key for the Student entity using the HasKey method. We then configure the
StudentRegdNumber property not to be generated as an identity column by using
the ValueGeneratedNever method.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configure Primary Key using HasKey method

modelBuilder.Entity<Student>().HasKey(s => s.StudentRegdNumber);

//Switching off Identity for StudentRegdNumber Column

modelBuilder.Entity<Student>()

.Property(p => p.StudentRegdNumber)

.ValueGeneratedNever(); // Turn off identity/auto-increment


859

public DbSet<Student> Students { get; set; }

}
This configuration tells EF Core not to generate values for the StudentRegdNumber column,
and it will be our responsibility to provide unique values when inserting new records. This
can be useful when we have an existing database with specific primary key values that we
want to manage manually.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project. This is because we are going to update the Identity
Column.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Next, to test whether Identity is off, add two new entities into the Students table with user-
defined values for the Primary key column. So, modify the Main method of the Program
class as follows:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)


860

try

//Create a Few Students with Explicit StudentRegdNumber values

Student std1 = new Student()

StudentRegdNumber = 101,

StudentRollNumber = 5001,

FirstName = "Pranaya",

LastName = "Rout"

};

Student std2 = new Student()

StudentRegdNumber = 102,

StudentRollNumber = 5011,

FirstName = "Hina",

LastName = "Sharma"

};

//Create the Context Object

using var context = new EFCoreDbContext();

//Add both the Student Entities to the Context Object

context.Add(std1);

context.Students.Add(std2);

//Call the Save Changes Method to add these two entities into the Students database table
861

context.SaveChanges();

Console.WriteLine("Student Addedd");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Now, run the application and verify the Students database table, and you should see the
following two records in the database with the explicitly provided StudentRegdNumber.

Configuring a Composite Primary Key using EF Core Fluent API:


Configuring a composite primary key (a primary key consisting of multiple columns) is also
possible using the Entity Framework Core Fluent API HasKey method. In this case, we
need to create an anonymous object, and using lambda expression, we need to specify
multiple properties. For a better understanding, please modify the context class as follows.
Here, we are making the Primary key with the combination of
the StudentRegdNumber and StudentRollNumber properties of the Student Entity. In the
case of composite primary, it will not create an identity column.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

{
862

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configure composite primary key using HasKey method

modelBuilder.Entity<Student>()

.HasKey(s => new { s.StudentRegdNumber, s.StudentRollNumber });

public DbSet<Student> Students { get; set; }

}
In this example, the Student entity has a composite primary key consisting of two
properties: StudentRegdNumber and StudentRollNumber.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project as we modify the Primary key and Identity column.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
863

Now, if you verify the database, you should see the Primary key created on both the
StudentRegdNumber and StudentRollNumber columns of the Students table in the
database, as shown in the image below.

Creating Primary Key on String Property using EF Core Fluent API:


It is possible to make String Property as a Primary key column using EF Core Fluent API. In
this case, it will create the column as the primary key column in the database, but without
identity. Let us first update the Student Entity as follows. As you can see, we have
created StudentRegdNumber as a string property, and we also want to mark this property
as our Primary Key column in the database.
namespace EFCoreCodeFirstDemo.Entities

public class Student


864

public string StudentRegdNumber { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

}
Now, modify the context class as follows. Here, we are making the FirstName Property of
the Student Entity as the Primary Key column in the database. In our DbContext class, we
need to use the Fluent API in the OnModelCreating method to configure the primary key for
the entity.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configure composite primary key using HasKey method

modelBuilder.Entity<Student>()
865

.HasKey(s => s.StudentRegdNumber); //Configure 'StudentRegdNumber' as the primary key

public DbSet<Student> Students { get; set; }

}
With this configuration, EF Core will treat the StudentRegdNumber property as the primary
key when generating the database schema. It’s important to note that string primary keys
are supported in EF Core, but you should ensure that the values you assign to this property
are unique, as primary keys must be unique within the table to maintain database integrity.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project as we modify the Primary key and Identity column.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

If you verify the database, you should see the Primary key created on the
StudentRegdNumber column of the Students table in the database, which type is nvarchar,
as shown in the image below.
866

Note: Remember that a primary key uniquely identifies each row in a table, so make sure
the chosen properties or combination of properties are unique within the entity. EF Core
also supports conventions, so if your entity follows the convention of having a property
named Id and Entity Name + ID, EF Core will automatically consider it the primary key.
However, you can override this convention using the Fluent API if needed.

Property Configuration using Entity Framework Core Fluent API


In this article, I will discuss Property Configuration using Entity Framework Core Fluent
API with Examples. Please read our previous article discussing Configuring Primary Key
and Composite Primary Key using Entity Framework Core Fluent API with Examples.
At the end of this article, you will understand the following pointers.
1. How to Configure Column Name, Type, and Order using EF Core Fluent
API?
2. How to Configure Column Size Using Entity Framework Core Fluent
API?
3. How to Configure Null or NotNull Columns using Entity Framework Core
Fluent API?
4. How to Ignore Table and Column Mapping using EF Core Fluent API?
How to Configure Column Name, Type, and Order using EF Core Fluent
API?
As we already discussed, the Entity Framework Core default convention will create a
column in the database for a property with the same name, same order, and same datatype
of the Property of an Entity. We have already discussed how to override this default
configuration using Entity Framework Data Annotation Attribute.
Now, we will see how we can override the default convention using Entity Framework Core
Fluent API to configure Column Name, Type, and Order. To configure the column name, we
need to use the HasColumnName method. To configure the column order, we need to use
867

the HasColumnOrder method, and to configure the column data type; we need to use
the HasColumnType method.
 HasColumnName(): We need to use the EF Core Fluent API
HasColumnName method to configure the column name for a property. This
method allows us to specify the name of the column in the database that
corresponds to the property.
 HasColumnType(): We need to use the EF Core Fluent API HasColumnType
method to configure the data type of a column. This method allows us to
specify the data type that the column should have in the database.
 HasColumnOrder(): We need to use the EF Core Fluent API
HasColumnOrder method to configure the order of a column in the database
table. This method allows us to specify the position of the column relative to
other columns in the table.
Let us understand this with an example. First, modify the Student Entity as follows.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:19 / 03:1710 Sec

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime DateOfBirth { get; set; }

}
Next, modify the context class as follows. Here, we set the Column’s Name, Type, and
Order. The Column Order will only work if we create the database table for the first time,
and if we apply the Order for all the properties of the entity, else the order will not work.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

{
868

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configure Column Name, Order, and Type

// Configure DateOfBirth column

modelBuilder.Entity<Student>()

.Property(p => p.DateOfBirth)

.HasColumnName("DOB")

.HasColumnOrder(2)

.HasColumnType("datetime2");

// Configure StudentId column

modelBuilder.Entity<Student>()

.Property(p => p.StudentId)

.HasColumnType("int")

.HasColumnOrder(0);

// Configure FirstName column


869

modelBuilder.Entity<Student>()

.Property(p => p.FirstName)

.HasColumnType("nvarchar(50)")

.HasColumnOrder(1);

// Configure LastName column

modelBuilder.Entity<Student>()

.Property(p => p.LastName)

.HasColumnOrder(3);

public DbSet<Student> Students { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

If you verify the database, you should see the column with the proper name, order, and
type, as expected, as shown in the image below.
870

How to Configure Column Size Using Entity Framework Core Fluent


API?
It is also possible to set the column size using Entity Framework Core Fluent API. By
default, for string or byte[] properties of an entity, EF Core will set the size of the database
column as max. For string properties, it will create the column as nvarchar(max), and
for byte[] properties, it will create the column as varbinary(max). And for
the decimal property, it will create the database column as decimal(18, 2).
To override the default size, we need to use the following HasMaxLength and HasPrecision
Fluent API Methods in Entity Framework.
 HasMaxLength(int? value): To configure the size (length) of a string column,
we need to use the HasMaxLength method in the Fluent API. This allows us
to specify the maximum number of characters that the column can store. To
configure the size (length) of a binary column (e.g., byte[]), you can use the
HasMaxLength method in the Fluent API as well. However, in this case, you
specify the maximum number of bytes for the column.
 HasPrecision(byte precision, byte scale): It configures the precision and
scale of the property. Here, the precision parameter specifies the precision of
the property, and the scale parameter specifies the scale of the property.
 IsFixedLength(): Configures the property as capable of storing only fixed-
length data, such as strings.
Let us understand the above Fluent API methods with an example. So, modify the Student
entity class as follows:
namespace EFCoreCodeFirstDemo.Entities

public class Student

{
871

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public decimal Height { get; set; }

public byte[] Photo { get; set; }

}
Let us first check the default data type and size of the columns, and then we will see how
we can change the size of the column using EF Core Fluent API. So, modify the context
class as follows.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }


872

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the database column with the default size
and data type, as shown in the below image.

Next, modify the context class as follows. We have applied the HasMaxLength method on
the FirstName, LastName, and Photo properties and the IsFixedLength method on the
LastName property. We have also applied the HasPrecision method on the Height Property.
873

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Set FirstName column size to 50, by default data type as nvarchar i.e. variable length

modelBuilder.Entity<Student>()

.Property(p => p.FirstName)

.HasMaxLength(50);

//Set LastName column size to 50 and change datatype to nchar

//IsFixedLength() change datatype from nvarchar to nchar

modelBuilder.Entity<Student>()

.Property(p => p.LastName)

.HasMaxLength(50)

.IsFixedLength();
874

//Set Height column size to decimal(2,2)

modelBuilder.Entity<Student>()

.Property(p => p.Height)

.HasPrecision(2, 2);

//Set Photo column size to 1024 Byte

modelBuilder.Entity<Student>()

.Property(p => p.Photo)

.HasMaxLength(50);

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig2. The name that you are giving it should not be
given earlier.

Now, if you verify the database, you should see the column with the appropriate size and
data type as expected, as shown in the image below.
875

How to Configure Null or NotNull Columns using Entity Framework Core


Fluent API?
In Entity Framework Core (EF Core), we can configure whether a column allows NULL
values using the Fluent API. First, let us modify the Student Entity as follows for a better
understanding. By default, the Entity Framework will create NULL for string Properties and
NOT NULL for int types in our example.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; } //NOT NULL By Default

public string FirstName { get; set; } //NULL By Default

public string MiddleName { get; set; } //NULL By Default

public string LastName { get; set; } //NULL By Default

}
Configure a Column to Allow NULL Values (Nullable):
876

To allow a column to accept NULL values, use the IsRequired(false) method in the EF Core
Fluent API configuration. By default, EF Core assumes that columns are nullable, so you
only need to use this method if you want to allow NULL values explicitly.
modelBuilder.Entity<Person>()

.Property(p => p.MiddleName)

.IsRequired(false); // MiddleName column allows NULL values


In this example, the MiddleName column of the Person entity is configured to allow NULL
values.
Configure a Column to Disallow NULL Values (NotNullable):
To make a column not nullable (i.e., disallow NULL values), omit the IsRequired method, as
EF Core assumes columns are not nullable by default. However, you can explicitly use
IsRequired(true) to make your intention clear.
modelBuilder.Entity<Person>()

.Property(p => p.LastName)

.IsRequired(); // LastName column does not allow NULL values


In this example, the LastName column of the Person entity is configured to disallow NULL
values.
So, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

}
877

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Configure NOT NULL for FirstName column which is by default NULL

modelBuilder.Entity<Student>()

.Property(p => p.FirstName)

.IsRequired(); //By Default True

//Configure NULL for LastName column which is by default NULL

modelBuilder.Entity<Student>()

.Property(p => p.LastName)

.IsRequired(true);

//Configure NULL for MiddleName column as NULL

modelBuilder.Entity<Student>()

.Property(p => p.MiddleName)

.IsRequired(false);

public DbSet<Student> Students { get; set; }

}
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
878

Now, verify the database, and you should see the columns with NULL and NOT NULL
constraints as expected, as shown in the below image.

Configuring columns correctly according to your database schema and business


requirements is important. By default, EF Core infers nullability from the CLR types of the
properties in your entity classes. However, using IsRequired(false) or IsRequired(true) in
the Fluent API provides explicit control over column nullability.
How to Ignore Table and Column Mapping using EF Core Fluent API?
In Entity Framework Core (EF Core), we can use the Fluent API to ignore the mapping of a
table or specific columns to our entity classes. Ignoring mapping can be useful when we
want to exclude certain tables or columns from being part of our EF Core model. We must
use the Ignore Method to Ignore Table and Column Mapping using Entity Framework Core
Fluent API.
Ignore Column Mapping using EF Core Fluent API:
To ignore the mapping of specific columns within an entity, we need to use the Ignore
method for each property we want to exclude from mapping to the database. This is
879

typically used when we have properties in our entity class that should not be persisted in the
database.
The Fluent API Ignore method is used to disable property mapping to a column in the
database. Let us understand this with an example. First, modify the Student Entity class as
follows.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Branch { get; set; }

}
Now, while generating the database for the above Student Entity, we do not want to map
the Branch property in the corresponding database. To Ignore the Branch property, we
need to modify the context class as follows. Here, you can see on the Student entity, we call
the Ignore method and specify the Branch property.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String


880

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Ignore the Branch Property while generating the database

modelBuilder.Entity<Student>().Ignore(t => t.Branch);

public DbSet<Student> Students { get; set; }

}
Note: It’s important to note that when you ignore a column using Ignore, the property won’t
be part of the database schema, and EF Core won’t generate SQL statements for it during
database operations.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, verify the database, and you will see that the Branch column is not in the Students
database, as shown in the image below. In this case, the Ignore method excludes property
from the model, so it will not be mapped to the database.
881

Ignore Table Mapping using Entity Framework Core Fluent API:


The EF Core Fluent API Ignore Method of the modelBuilder class can also exclude the
specified entity’s database mapping. Let us understand this with an example. Let us first
add the following Standard Entity.
namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int StandardId { get; set; }

public string StandardName { get; set; }

public string Description { get; set; }

}
Next, modify the context class as follows. The following example code will ensure that the
Standard table is not created in the database.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

{
882

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Ignore the Branch Property while generating the database

modelBuilder.Entity<Student>().Ignore(t => t.Branch);

//Ignore the Standard Entity while generating the database

modelBuilder.Ignore<Standard>();

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }

}
As you can see in the above code, we have created Standard Entity as a DbSet property.
But then we used the Ignore method and specified the Standard Entity, which will exclude
the Standard entity while generating the database.
Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and
Migration folder from our project.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
883

Now, verify the database, and you should see only the Students table but not the Standard
table in the database, as shown in the image below.

Bulk Operations in Entity Framework Core


In this article, I will discuss Bulk Operations (Bulk Insert, Bulk Update, and Bulk Delete)
in Entity Framework Core (EF Core) with Examples. Please read our previous article
discussing Property Configuration using Entity Framework Core Fluent API with
Examples.
Bulk Insert, Update, and Delete Operations in Entity Framework Core
Bulk Operations in Entity Framework Core refers to the ability to process large numbers of
records efficiently, such as inserting, updating, or deleting many rows in a single operation.
While Entity Framework Core excels in many areas, it is not optimized for bulk operations
out of the box. Each insert, update, or delete command is sent to the database individually,
which can lead to performance issues when dealing with large datasets.
In Entity Framework Core (EF Core), we can perform bulk INSERT, UPDATE, and DELETE
operations using various techniques. EF Core does not provide built-in support for bulk
operations like other ORMs, but we can implement these operations efficiently by utilizing
different methods and libraries.
884

Examples to Understand Bulk Insert, Update, and Delete Operations in


Entity Framework Core
To understand How to Perform Bulk Operations using Entity Framework Core (EF Core),
we will use the following Student Entity. So, create a class file named Student.cs and copy
and paste the following code.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:10 / 03:1710 Sec

namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Branch { get; set; }

}
Next, modify the Context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated SQL


885

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

With this, our Database with Students database table is created, as shown in the below
image:
886

Bulk Insert in Entity Framework Core:


Bulk Insert is the process of efficiently inserting many records into a database table. In
Entity Framework Core, we need to use the DbSet AddRange() or the DbContext
AddRange method to add a collection of entities in one go. The AddRange() method
attaches a collection of entities to the context object with the Added state. When we call the
SaveChanges method, it will execute the INSERT SQL Command in the database for all the
entities.
For a better understanding, please modify the Program class as follows. In the following
example, within the Main method, first, we create a student collection with four students,
and we want to insert these four students into the database. So, we pass this student
collection to the BulkInsert method. The BulkInsert method uses the DbSet.AddRange or
DbContext.AddRange method to attach all the student entities with the Added State in the
context object. When we call the SaveChanges method, the Entity Framework generates
and executes the INSERT SQL Statement into the database. Then, to display the Entities,
we call the GetStudents method.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

{
887

try

List<Student> newStudents = new List<Student>() {

new Student() { FirstName = "Pranaya", LastName = "Rout", Branch= "CSE" },

new Student() { FirstName = "Hina", LastName = "Sharma", Branch= "CSE" },

new Student() { FirstName = "Anurag", LastName= "Mohanty", Branch= "CSE" },

new Student() { FirstName = "Prity", LastName= "Tiwary", Branch= "ETC" }

};

BulkInsert(newStudents);

//Display all the Students whose Branch is CSE

GetStudents("CSE");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void BulkInsert(IList<Student> newStudents)

using (var context = new EFCoreDbContext())

//Using DbSet AddRange Method

context.Students.AddRange(newStudents);
888

//Using DbContext AddRange Method

//context.AddRange(newStudents);

context.SaveChanges();

public static void GetStudents(string Branch)

using (var context = new EFCoreDbContext())

//Fetch all the Students based on Branch

var studentsList = context.Students.Where(std => std.Branch.Equals(Branch,


StringComparison.InvariantCultureIgnoreCase));

foreach (var std in studentsList)

Console.WriteLine($"Id : {std.FirstName}, Name : {std.FirstName} {std.LastName},


Branch : {std.Branch}");

}
Output:
889

Note: This is one of the biggest changes they have made to the AddRange method in
Entity Framework Core. Using a Single Round Trip with the database, using the SQL
MERGE and INSERT Statement, they are performing the BULK Insert Operation. While the
performance is better in EF Core than the old EF6, the solution becomes very inefficient
when inserting thousands of entities.
Bulk Update in Entity Framework Core:
Bulk Updating is the process of efficiently updating many records into a database table. In
BULK UPDATE, first, we need to fetch all the records from the database that need to be
updated and then update the required properties of the entities. When we update the
properties of entities, it will mark the entity state as Modified. When we call the
SaveChanges() method, the Entity Framework Core will generate the UPDATE SQL
Statement to update the changes in the database.
For a better understanding, please modify the Program class as follows: In the BulkUpdate
method, first, we fetch all the students whose Branch is CSE and then update the
FirstName and LastName properties. Once we update the FirstName and LastName
properties, we call the SaveChanges method to update the data in the database. In this
case, the EF Core will generate and execute separate SQL UPDATE Statements in the
database.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try
890

//Update all the Students whose Branch is CSE

BulkUpdate("CSE");

//Display all the Students whose Branch is CSE

GetStudents("CSE");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void BulkUpdate(string Branch)

using (var context = new EFCoreDbContext())

//Fetch all the Students Based on Branch

var studentsList = context.Students.Where(std => std.Branch == Branch).ToList();

//Update the First Name and Last Name

foreach (var std in studentsList)

std.FirstName += "Changed";

std.LastName += "Changed";

}
891

//Update the Data Into the Database

context.SaveChanges();

public static void GetStudents(string Branch)

using (var context = new EFCoreDbContext())

//Fetch all the Students based on Branch

var studentsList = context.Students.Where(std => std.Branch == Branch).ToList();

foreach (var std in studentsList)

Console.WriteLine($"Id : {std.FirstName}, Name : {std.FirstName} {std.LastName},


Branch : {std.Branch}");

}
Output:
892

As you can see, using a single database connection, or you can say using a single
database round trip, it executes the three update statements in the database. So, it will
perform better than EF6, but it is not optimized. In our next article, I will show you how to
perform the BULK UPDATE operations in an optimized manner.
Note: By looking at the three UPDATE SQL Statements, please don’t think it makes three
database round trips. This is happening in Entity Framework, not in Entity Framework Core.
In EF Core, they have improved the performance at some level by sending all these three
statements in a single database round trip. The problem has occurred when you are going
to update thousands of records.
Bulk Delete in Entity Framework Core:
Bulk deleting involves removing a large number of records efficiently. In Entity Framework
Core, we can use the DbSet RemoveRange() method or the DbContext RemoveRange()
method to perform bulk delete operations. What the RemoveRange() method does is attach
a collection of entities to the context object with the Deleted state, and when we call the
SaveChanges method, it will execute the DELETE SQL Statement in the database for all
the entities.
For a better understanding, please modify the Program class as follows. In the below
example, we are fetching all the students from the Student database table where the
Branch is CSE and storing them in studentsList collection variable. Then, pass that list of
students to the RemoveRange method, marking the Entity State of all those students as
Deleted. When we call the SaveChanges method, it will generate and execute separate
DELETE SQL Statements into the database for each entity.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

{
893

static void Main(string[] args)

try

//Delete all the Students whose Branch is CSE

BulkDelete("CSE");

//Display all the Students whose Branch is CSE

//If Delete Operation Successful, then it should not display any data

GetStudents("CSE");

Console.ReadKey();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void BulkDelete(string Branch)

using var context = new EFCoreDbContext();

//Fetch all the Students Based on Branch

var studentsList = context.Students.Where(std => std.Branch == Branch).ToList();

//Mark all the Students Entity State as Deleted

context.Students.RemoveRange(studentsList);

//Delete All those Entities from the Database


894

context.SaveChanges();

public static void GetStudents(string Branch)

using (var context = new EFCoreDbContext())

//Fetch all the Students based on Branch

var studentsList = context.Students.Where(std => std.Branch == Branch).ToList();

if (studentsList.Count > 0 )

foreach (var std in studentsList)

Console.WriteLine($"Id : {std.FirstName}, Name : {std.FirstName} {std.LastName},


Branch : {std.Branch}");

else

Console.WriteLine($"No Records Found with the Branch : {Branch}");

}
895

Output:

Similar to Bulk Update Operation, in this case, it executes the three DELETE SQL
statements in the database using a single database round trip. That means it will also
perform better than EF6 but is not optimized. In our next article, I will show you how to
perform the BULK UPDATE operations in an optimized manner.
Note: By looking at the three DELETE SQL Statements, please don’t think it makes three
database round trips. This is happening in Entity Framework, not in Entity Framework Core.
In EF Core, they have improved the performance at some level by sending all these three
statements in a single database round trip. The problem occurs when you are going to
delete thousands of records.
While the performance is better in EF Core than the old EF6, the solution becomes very
inefficient when inserting thousands of entities. The fastest way of inserting multiple data is
by using the Entity Framework Extensions third-party library. Depending on the provider,
performance can be increased by up to 50x faster and more.

Bulk Operations using Entity Framework Core Extension


In this article, I will discuss Bulk Operations using Entity Framework Core
Extension with Examples. Please read our previous article, discussing Bulk Insert,
Update, and Delete Operations in Entity Framework Core with Examples. At the end of
this article, you will understand the following pointers:
1. Bulk Operations using Entity Framework Core Extension
2. Installing Z.EntityFramework.Extensions.EFCore
3. Example to Understand Bulk Operations using Entity Framework Core
Extension
4. Bulk Insert using Entity Framework Core Extension
5. Performance Comparison Between SaveChanges and BulkInsert in EF
Core
6. Bulk Update using Entity Framework Core Extension
896

7. Performance Comparison Between SaveChanges and BulkUpdate


Extension Method
8. Bulk Delete using Entity Framework Core Extension
9. Performance Comparison Between SaveChanges and BulkDelete
Extension Method
10. Advantages of using Entity Framework Core Extension
11. Disadvantages of using Entity Framework Core Extension
Bulk Operations using Entity Framework Core Extension
In Entity Framework Core (EF Core), performing bulk insert, update, and delete efficiently
for many records can be challenging because EF Core does not natively support bulk insert,
update, and delete operations. However, we can use third-party libraries and techniques to
achieve bulk insert efficiently, update, and delete operations. One commonly used library for
bulk operations in EF Core is Entity Framework Extensions (EF Extensions). Let us proceed
and try to understand how to perform bulk insert, update, and delete operations using EF
Extensions:
Note: Unfortunately, EF Core doesn’t have a formal API for performing any bulk operations,
so to improve your performance with bulk operations, you will need to use the Entity
Framework Extensions library created by ZZZ Projects.
Installing Z.EntityFramework.Extensions.EFCore:
Now, I will show you how to use Z.EntityFramework.Extensions.EFCore package and
perform Bulk Insert, Update, and Delete Operations with Entity Framework Core. First, we
need to install the Entity Framework Extensions (EF Extensions) NuGet package in your
project.
Ad
1/2
00:11

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
First, open the NuGet Package Manager for Solution and
search Z.EntityFramework.Extensions.EFCore package. Select
Z.EntityFramework.Extensions.EFCore, select the Project, choose the latest version, and
finally click the Install button, as shown in the image below.

You can also install it using the NuGet Package Manager Console as follows:
Install-Package Z.EntityFramework.Extensions.EFCore
897

Once you install the Z.EntityFramework.Extensions.EFCore package, you can verify the
same inside the Packages folder as shown in the below image.

Entity Framework Extensions extend your DbContext with high-performance bulk


operations: BulkSaveChanges, BulkInsert, BulkUpdate, BulkDelete, BulkMerge, and more.
It Supports SQL Server, MySQL, Oracle, PostgreSQL, SQLite, and more. For more
information, please check the below two links.
GitHub Link: https://www.nuget.org/packages/Z.EntityFramework.Extensions.EFCore/
Official Website: https://entityframework-extensions.net/bulk-extensions
Example to Understand Bulk Operations using Entity Framework Core
Extension:
To understand How to Perform Bulk Operations using Entity Framework Core (EF Core),
we will use the following Student Entity. So, create a class file named Student.cs and copy
and paste the following code.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Branch { get; set; }

}
898

Next, modify the Context class as follows:


using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated SQL

//optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
899

With this, our Database with Students database table is created, as shown in the below
image:

Bulk Insert using Entity Framework Core Extension:


The Z.EntityFramework.Extensions.EFCore provides two methods,
i.e., BulkInsert and BulkInsertAync, which allow us to insert many entities into the
database in one go.
For a better understanding, please modify the Program class as follows: In the below
example, we are Inserting the Student Lists into the database using the BulkInsert
Extension Method. We don’t need to call the SaveChanges method while performing the
Bulk Insert Operations. In this case, the context class will perform the INSERT operation
using a single round trip using an optimized SQL query.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo
900

internal class Program

static void Main(string[] args)

try

//Create a List of Students

List<Student> newStudents = new List<Student>() {

new Student() { FirstName = "Pranaya", LastName = "Rout", Branch= "CSE" },

new Student() { FirstName = "Hina", LastName = "Sharma", Branch= "CSE" },

new Student() { FirstName = "Anurag", LastName= "Mohanty", Branch= "CSE" },

new Student() { FirstName = "Prity", LastName= "Tiwary", Branch= "ETC" }

};

using var context = new EFCoreDbContext();

// Performing BULK INSERT

// Easy to use. No need to call SaveChanges

context.BulkInsert(newStudents);

Console.WriteLine("BulkInsert Method Completed");

// Display all Students who are belongs to CSE Branch

GetStudents("CSE");

Console.ReadKey();

catch (Exception ex)


901

Console.WriteLine($"Error: {ex.Message}"); ;

public static void GetStudents(string Branch)

using (var context = new EFCoreDbContext())

//Fetch all the Students based on Branch

var studentsList = context.Students.Where(std => std.Branch == Branch).ToList();

foreach (var std in studentsList)

Console.WriteLine($"Id : {std.FirstName}, Name : {std.FirstName} {std.LastName},


Branch : {std.Branch}");

}
Performance Comparison Between SaveChanges and BulkInsert in EF
Core:
Let’s see the performance benchmark between the Entity Framework Core SaveChanges
method and the BulkInsert Extension Method with an example. We will Insert 1000 students
using both approaches (i.e., AddRange with SaveChanges Method and BulkInsert
Extension Method). We will measure the time taken to complete the task by both
approaches.
For a better understanding, please modify the Program class as follows. In the below code,
please don’t consider the FirstTimeExecution method for performance testing, as we know it
will take longer to execute something for the first time.
902

using EFCoreCodeFirstDemo.Entities;

using System.Diagnostics;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//Don't consider below for performance Testing

//This warmup

FirstTimeExecution();

// Generate 1000 Students

List<Student> studentList = GenerateStudents(1000);

Stopwatch SaveChangesStopwatch = new Stopwatch();

Stopwatch BulkInsertStopwatch = new Stopwatch();

using (EFCoreDbContext context1 = new EFCoreDbContext())

// Add the Student Collection using the AddRange Method

context1.Students.AddRange(studentList);

SaveChangesStopwatch.Start();

context1.SaveChanges();

SaveChangesStopwatch.Stop();
903

Console.WriteLine($"SaveChanges, Entities : {studentList.Count}, Time Taken :


{SaveChangesStopwatch.ElapsedMilliseconds} MS");

using (EFCoreDbContext context2 = new EFCoreDbContext())

// BulkInsert Extension Method

BulkInsertStopwatch.Start();

context2.BulkInsert(studentList, options => options.AutoMapOutputDirection = false); //


performance can be improved with options

BulkInsertStopwatch.Stop();

Console.WriteLine($"BulkInsert, Entities : {studentList.Count}, Time Taken :


{BulkInsertStopwatch.ElapsedMilliseconds} MS");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void FirstTimeExecution()

//Generate a List of 20 Students

List<Student> stduentsList = GenerateStudents(20);

//Using AddRange and SaveChanges Method


904

using (var context = new EFCoreDbContext())

context.Students.AddRange(stduentsList);

//Call the SaveChanges Method to INSERT the data into the database

context.SaveChanges();

// Delete the Newly Inserted Data

context.BulkDelete(stduentsList);

// Using BulkInsert Extension Method

using (var context = new EFCoreDbContext())

//Insert the Bulk Data

context.BulkInsert(stduentsList);

// Delete the Newly Inserted Data

context.BulkDelete(stduentsList);

//This Method is going to generate list of Students

//count: The number of Students to be generated

public static List<Student> GenerateStudents(int count)

var listOfStudents = new List<Student>();

for (int i = 0; i < count; i++)

{
905

listOfStudents.Add(new Student() { FirstName = "FirstName_" + i, LastName =


"LastName_" + i, Branch = "CSE" });

return listOfStudents;

}
Run the above code, and you will get the following output. As you can see, the
SaveChanges method took 85 MS to insert 1000 entities into the database, while the
BulkInsert Extension method took only 38 MS to insert 1000 entities into the database. So,
you can imagine how dangerous Entity Framework Core AddRange and SaveChanges
methods are when performance is considered. Many factors that affect the benchmark time
need to be considered, such as index, column type, latency, throttling, etc.

Bulk Update using Entity Framework Core Extension:


The Z.EntityFramework.Extensions.EFCore provides two methods, i.e., BulkUpdate and
BulkUpdateAync, to extend our DbContext object, allowing us to update many entities in the
database in one go.
For a better understanding, please modify the Program class as follows: In the below
example, first, we fetch all the students whose Branch is CSE and then update the First
Name and Last Name, and finally update the updated data to the database using the
BulkUpdate Extension Method.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

{
906

Console.WriteLine("BulkUpdate Method Started");

BulkUpdate("CSE");

Console.WriteLine("BulkUpdate Method Completed");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void BulkUpdate(string Branch)

using var context = new EFCoreDbContext();

//Fetch all the students whose StandardId is 1

var studentsList = context.Students.Where(std => std.Branch == Branch);

//Update the Firstname and LastName of all Stduents

foreach (var std in studentsList)

std.FirstName += " Changed";

std.LastName += " Changed";

// Call the BulkUpdate Extension Method to perform the Bulk Update Operation

context.BulkUpdate(studentsList);

}
907

}
Performance Comparison Between SaveChanges and BulkUpdate
Extension Method:
Before Proceeding further, please truncate the Student table using the following syntax:
truncate table Students
With an Example, let’s see the performance benchmark between the SaveChanges and
BulkUpdate Extension Method in Entity Framework Core. In the example below, first, we
insert 1000 students into the student table, and then we will update the first and last names
of all the students using both approaches. Here, we will also print the time taken to
complete the task by both approaches.
using EFCoreCodeFirstDemo.Entities;

using System.Diagnostics;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

//First Add 1000 Students

AddStudents(1000);

Stopwatch SaveChangesStopwatch = new Stopwatch();

Stopwatch BulkUpdateStopwatch = new Stopwatch();

//Bulk Update using SaveChanges

using (var context = new EFCoreDbContext())

{
908

//Fetch all the Students

var studentList = context.Students.ToList();

foreach (var std in studentList)

std.FirstName += "_UpdateBySaveChanges";

std.LastName += "_UpdateBySaveChanges";

SaveChangesStopwatch.Start();

//Call the SaveChanges Method to Update the Data in the Database

context.SaveChanges();

SaveChangesStopwatch.Stop();

Console.WriteLine($"SaveChanges, Entities : {studentList.Count}, Performance :


{SaveChangesStopwatch.ElapsedMilliseconds} MS");

//Bulk Update using BulkUpdate Extension Method

using (var context = new EFCoreDbContext())

//Fetch all the Students

var studentList = context.Students.ToList();

foreach (var std in studentList)

std.FirstName += "_UpdatedByBulkUpdate";

std.LastName += "_UpdatedByBulkUpdate";

}
909

BulkUpdateStopwatch.Start();

//Call the BulkUpdate Method to Update the Data in the Database

context.BulkUpdate(studentList);

BulkUpdateStopwatch.Stop();

Console.WriteLine($"BulkUpdate, Entities : {studentList.Count}, Performance :


{BulkUpdateStopwatch.ElapsedMilliseconds} MS");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void AddStudents(int count)

//Add 1000 Students for Performance Testing

var stduentsList = GenerateStudents(count);

// Bulk Insert using BulkInsert Extension Method

using var context = new EFCoreDbContext();

context.BulkInsert(stduentsList, options => options.AutoMapOutputDirection = false);

//This Method is going to generate list of Students

//count: The number of Students to be generated


910

public static List<Student> GenerateStudents(int count)

var listOfStudents = new List<Student>();

for (int i = 0; i < count; i++)

listOfStudents.Add(new Student() { FirstName = "FirstName_" + i, LastName =


"LastName_" + i, Branch = "CSE" });

return listOfStudents;

}
When you run the above code, you will get the following output. As you can see, the
SaveChanges method took 241 MS time to update 1000 records into the database, while
the BulkUpdate Extension Method took 137 MS to update the same 1000 records. So,
clearly, there is a huge performance difference between both approaches. So, in real-time
applications, you should avoid using the SaveChanges method if performance is the major
factor while performing BULK UPDATE.

Bulk Delete using Entity Framework Core Extension


The BulkDelete and BulkDeleteAync methods extend our DbContext object, allowing us to
delete many entities into the database with a single round trip, improving the application’s
performance. For a better understanding, please modify the Program class as follows: In
the below example, first, we fetch all the students where the Branch is CSE, and then we
delete the retrieved student using the BulkDelete Extension Method.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

internal class Program

{
911

static void Main(string[] args)

try

Console.WriteLine("BulkDelete Method Started");

BulkDelete();

Console.WriteLine("BulkDelete Method Completed");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void BulkDelete()

using (var context = new EFCoreDbContext())

//Fetch all the students from the database where Branch == "CSE"

var StudentsListToBeDeleted = context.Students.Where(std => std.Branch == "CSE");

// Call the BulkDelete Extension Method to perform the Bulk Delete Operation

context.BulkDelete(StudentsListToBeDeleted);

}
912

}
Performance Comparison Between SaveChanges and BulkDelete
Extension Method:
Let us see the performance benchmark between the SaveChanges and BulkDelete
Extension Method in Entity Framework Core with an Example. In the below example, first,
we are inserting 1000 students into the student table. Then, we will delete the same 1000
records using the SaveChanges method. Then again, we insert 1000 students into the
student table and then delete those 1000 records using the BulkDelete Extension Method.
Here, we also print the time taken to complete the task by both approaches.
using EFCoreCodeFirstDemo.Entities;

using System.Diagnostics;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try

Stopwatch SaveChangesStopwatch = new Stopwatch();

Stopwatch BulkDeleteStopwatch = new Stopwatch();

//Bulk DELETE using SaveChanges

using (var context = new EFCoreDbContext())

//First Add 1000 Students

AddStudents(1000);

//Then Fetch the Students to be Deleted


913

var StudentListToBeDeleted = context.Students.Where(std => std.StudentId > 4).ToList();

int StudentCount = StudentListToBeDeleted.Count;

//Then Call the Remove Range method which will mark the Entity State as Deleted

context.Students.RemoveRange(StudentListToBeDeleted);

//Start the StopWatch

SaveChangesStopwatch.Start();

//Call the SaveChanges Method to Delete the Data in the Database

context.SaveChanges();

//Stop the StopWatch

SaveChangesStopwatch.Stop();

Console.WriteLine($"SaveChanges, Entities : {StudentCount}, Performance :


{SaveChangesStopwatch.ElapsedMilliseconds} MS");

//Bulk DELETE using BulkDelete Extension Method

using (var context = new EFCoreDbContext())

//First Add 1000 Students

AddStudents(1000);

//Then Fetch the Students to be Deleted

var StudentListToBeDeleted = context.Students.Where(std => std.StudentId > 4).ToList();

int StudentCount = StudentListToBeDeleted.Count;

//Start the StopWatch

BulkDeleteStopwatch.Start();

//Call the BulkDelete Method to Delete the Data in the Database


914

context.BulkDelete(StudentListToBeDeleted);

//Stop the StopWatch

BulkDeleteStopwatch.Stop();

Console.WriteLine($"BulkUpdate, Entities : {StudentCount}, Performance :


{BulkDeleteStopwatch.ElapsedMilliseconds} MS");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static void AddStudents(int count)

//Add Students for Performance Testing

var stduentsList = GenerateStudents(count);

// Bulk Insert using BulkInsert Extension Method

using (var context = new EFCoreDbContext())

context.BulkInsert(stduentsList, options => options.AutoMapOutputDirection = false);

public static List<Student> GenerateStudents(int count)


915

List<Student> Studentlist = new List<Student>();

for (int i = 0; i < count; i++)

Studentlist.Add(new Student() { FirstName = "FirstName_" + i, LastName = "LastName_" +


i, Branch = "CSE" });

return Studentlist;

}
When you run the above code, you will get the following output. As you can see, the
SaveChanges method took 183 MS to delete 1000 records from the database, while the
BulkDelete Extension Method took 127 MS to delete the same 1000 records. So, clearly,
there is a huge performance difference between both approaches. So, in real-time
applications, while performing BULK DELETE, you should avoid using the SaveChanges
method if performance is the major factor and go with the BulkDelete Extension method.

Advantages of using Entity Framework Core Extension


Entity Framework Core Extensions (EF Extensions) is a library that provides various
features and extensions for Entity Framework Core (EF Core). Using EF Extensions can
offer several advantages and benefits when working with EF Core in your application:
 Improved Performance: EF Extensions is designed to optimize database
operations, such as bulk insert, bulk update, and bulk delete. These
operations are significantly faster than their standard EF Core counterparts,
especially when dealing large datasets. The library uses efficient batch
processing and generates optimized SQL queries to reduce database round-
trips and improve performance.
 Simplified Bulk Operations: Performing bulk insert, update, and delete
operations in EF Core can be challenging and error-prone when using
standard approaches. EF Extensions simplify these operations by providing
easy-to-use methods for bulk operations, reducing the complexity of writing
and maintaining custom code.
 Reduced Database Round-Trips: EF Extensions minimize the number of
database round-trips by batching multiple records into a single operation. This
916

can lead to substantial performance improvements, particularly in scenarios


involving large data imports or updates.
 Transaction Support: EF Extensions provides built-in transaction support for
bulk operations. You can execute bulk operations within a transaction,
ensuring data consistency and the ability to roll back changes if an error
occurs.
 Optimized SQL Queries: The library generates optimized SQL queries for
bulk operations, taking advantage of database-specific features and
optimizations. This results in more efficient database interactions and better
performance.
 Cross-Platform Compatibility: EF Extensions is compatible with various
database providers EF Core supports, including SQL Server, MySQL,
PostgreSQL, Oracle, SQLite, and more. This cross-platform support allows
you to use the library with different database systems.
 Developer Productivity: Using EF Extensions can enhance developer
productivity by simplifying the implementation of bulk operations and reducing
the need for writing complex custom code. This can lead to faster
development and easier maintenance.
 Flexible Licensing: EF Extensions offers different licensing options,
including a free Community Edition for basic functionality and paid editions
(Professional and Enterprise) with additional features and support.
Developers can choose the edition that best fits their needs and budget.
 Active Development and Support: EF Extensions is actively developed and
supported by ZZZ Projects. This ensures that the library remains up-to-date
with EF Core and database provider changes and provides timely bug fixes
and improvements.
 Community and Documentation: EF Extensions has an active user
community and comprehensive documentation, including tutorials and
examples. This makes it easier for developers to get started and troubleshoot
issues.
While EF Extensions offer numerous advantages, they may not be necessary for all
projects. Evaluate your specific application requirements and performance needs to
determine whether EF Extensions suit your EF Core-based application.
Disadvantages of using Entity Framework Core Extension
While Entity Framework Core Extensions (EF Extensions) provide various advantages and
benefits, there are also some potential disadvantages and considerations to be aware of
when using this library:
 Additional Dependency: Introducing EF Extensions into your project adds
an additional dependency, which may increase the size of your application
and require you to manage updates and compatibility with new versions of EF
Extensions.
 Licensing Costs: While EF Extensions offers a free Community Edition with
basic functionality, more advanced features and support may require a paid
license (Professional or Enterprise Edition). Licensing costs can be a
consideration for some projects.
 Learning Curve: Learning how to use EF Extensions effectively, including its
specific API and methods, may require additional effort and time for
developers new to the library.
917

 Compatibility and Maintenance: EF Extensions may need to be updated to


remain compatible with new versions of Entity Framework Core (EF Core)
and database providers. Ensuring that your project remains compatible and
that any potential breaking changes are addressed can require maintenance
effort.
 Limited to Specific Use Cases: EF Extensions is primarily designed for
optimizing bulk operations, such as bulk insert, bulk update, and bulk delete.
If your application does not require frequent or large-scale bulk operations,
the library’s benefits may not justify its use.
 Reduced Control Over SQL Queries: While EF Extensions generate
optimized SQL queries for bulk operations, this can also mean reduced
control over the specific SQL statements being executed. In some cases,
advanced users who need precise control over query optimization may prefer
to write custom SQL scripts.
 Transaction Management: While EF Extensions supports transactions for
bulk operations, managing transactions and error handling can become more
complex when performing bulk operations, especially if multiple operations
are involved within a single transaction.
 Compatibility With Database Providers: While EF Extensions aims to
support multiple database providers, not all database providers may be fully
compatible or supported. Testing and ensuring compatibility with your specific
database provider may be necessary.
 Resource Utilization: Performing bulk operations can consume significant
system resources, especially in scenarios involving very large datasets.
Proper resource management and monitoring may be required to prevent
performance issues.
Consideration of Database Constraints: When performing bulk operations, it’s important to
consider database constraints (e.g., unique constraints, foreign key constraints) and ensure
that data integrity is maintained during the operations.

Points to Remember:
Entity Framework Extensions (EF Extensions), which ZZZ Projects develop, offers both free
and paid versions. The library provides various features for optimizing and simplifying bulk
operations in Entity Framework Core.
 Free Version (Community Edition): EF Extensions offers a free Community
Edition that provides basic functionality for bulk operations. This free version
includes features like bulk insert, bulk update, bulk delete, and more. It can
be used in your projects without requiring a license.
 Paid Versions (Professional and Enterprise Editions): EF Extensions also
offers paid versions, such as the Professional and Enterprise Editions. These
paid versions provide additional features, performance enhancements, and
support. Licensing fees may apply for the Professional and Enterprise
Editions, and the pricing may vary based on the specific edition and usage
requirements.
918

Asynchronous Programming with Entity Framework Core


In this article, I will discuss Asynchronous Programming with Entity Framework Core
(EF Core) with Examples. Please read our previous article discussing Bulk Operations
using Entity Framework Core Extension with Examples. At the end of this article, you will
understand the following pointers.
1. Asynchronous Programming with Entity Framework Core
2. Example to Understand Asynchronous Programming with EF Core
3. Asynchronous Query in Entity Framework Core
4. Asynchronous Save using Entity Framework Core
5. Asynchronous Delete in Entity Framework Core
6. Asynchronous Operation with Cancellation Token in EF Core
7. Advantages of Asynchronous Programming with Entity Framework Core
8. Disadvantages of Asynchronous Programming with Entity Framework
Core
Asynchronous Programming with Entity Framework Core
Asynchronous programming with Entity Framework Core (EF Core) allows us to perform
database operations efficiently without blocking the main (UI) thread or the calling thread in
a non-UI application. This is especially important for applications that need to remain
responsive and scalable.
Asynchronous programming in Entity Framework Core allows for non-blocking database
operations, enhancing the scalability and responsiveness of applications, particularly those
with I/O-bound operations, such as web applications. Asynchronous operations in EF Core
are achieved using the async and await keywords in C#, along with asynchronous versions
of EF Core methods.
Use Async/Await Keywords:
To perform asynchronous database operations, we should use the async and await
keywords in our C# code. These keywords enable non-blocking execution and allow our
application to continue processing other tasks while waiting for the database operation to
complete.
Ad
1/2
00:35

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV
Async Methods in EF Core:
Entity Framework Core (EF Core) provides asynchronous methods for most synchronous
methods. For example, you can use ToListAsync, FirstOrDefaultAsync,
SaveChangesAsync, etc.
// Synchronous query

var students = context.Students.ToList();

// Asynchronous query
919

var studentsAsync = await context.Students.ToListAsync();


The asynchronous methods typically have “Async” suffixes, making it easy to identify their
asynchronous nature.
DbContext Lifetime:
Ensure the DbContext instance you use for asynchronous operations has a suitable lifetime.
In web applications, using a scoped or per-request DbContext is common to ensure that it’s
disposed of at the appropriate time.
Exception Handling:
Handle exceptions that may occur during asynchronous operations. You can use try/catch
blocks to catch and handle exceptions thrown by EF Core or the underlying database.
try

// Perform asynchronous database operation

catch (Exception ex)

// Handle the exception

}
Cancellation:
You can pass a CancellationToken to asynchronous methods to allow for task cancellation.
This is useful for scenarios where you want to cancel a database operation that is taking too
long.
var cancellationToken = new CancellationToken();

var studentsAsync = await context.Students

.ToListAsync(cancellationToken);
Async Database Updates:
You can use SaveChangesAsync for asynchronous database updates. This is especially
important when making multiple changes to the database within a single transaction.
// Asynchronous database update

await context.SaveChangesAsync();
Avoid Mixing Synchronous and Asynchronous Code:
Avoid mixing synchronous and asynchronous code within a single method or operation, as it
can lead to deadlocks or inefficient code. Stick to asynchronous code when performing
database operations asynchronously.
920

Example to Understand Asynchronous Programming with EF Core:


To understand Asynchronous Programming with Entity Framework Core (EF Core), we will
use the following Student Entity. So, create a class file named Student.cs and copy and
paste the following code.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Branch { get; set; }

}
Next, modify the Context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");
921

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

With this, our Database with Students table is created, as shown in the below image:
922

Please execute the following SQL Script to insert demo data into the Students database
table.
INSERT INTO Students Values ('Pranaya', 'Rout', 'CSE');

INSERT INTO Students Values ('Hina', 'Sharma', 'CSE');

INSERT INTO Students Values ('Priyanka', 'Dewangan', 'CSE');

INSERT INTO Students Values ('Anurag', 'Mohanty', 'ETC');


Example to Understand Asynchronous Query in Entity Framework Core:
For a better understanding, please modify the Program class as follows:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

internal class Program

static void Main(string[] args)

try
923

//Environment.CurrentManagedThreadId will return the currently executing unique thread id

Console.WriteLine($"Main Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

var query = GetStudent(2);

Console.WriteLine($"Main Method Doing Some Other Task by ThreadId:


{Environment.CurrentManagedThreadId}");

//Here, we are blocking the Main thread to get the result from the GetStudent Method

var student = query.Result;

Console.WriteLine($"Name: {student?.FirstName} {student?.LastName}");

Console.WriteLine($"Main Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

private static async Task<Student?> GetStudent(int StudentId)

Console.WriteLine($"GetStudent Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

Student? student = null;

using (var context = new EFCoreDbContext())


924

Console.WriteLine($"FirstOrDefaultAsync Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

student = await (context.Students.FirstOrDefaultAsync(s => s.StudentId == StudentId));

Console.WriteLine($"FirstOrDefaultAsync Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

Console.WriteLine($"GetStudent Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

return student;

}
Output:

As you can see in the above output, the Main method is started and completed by a thread
whose ID is 1. But please focus on the GetStudent method. It is started by the Thread,
whose ID is 1. But once it starts executing the FirstOrDefaultAsync asynchronous method,
the Thread whose Id is 1 is released, and that thread starts executing the other code in the
Main method. Once the query execution is completed, another thread whose ID is 5 will
come from the thread pool and start executing the rest of the code inside the GetStudent
method.
Asynchronous Save using Entity Framework Core
The Entity Framework Core provides an asynchronous version of
the SaveChanges() method called the SaveChangesAsync() to save entities into the
database asynchronously. For a better understanding, please look at the example below,
which shows saving a Student Entity to the database asynchronously. In this case, when we
call the SaveChangesAsync() method, the calling thread will be released and do another
925

task. Once the SaveChangesAsync() method execution is completed, another thread pool
thread will come and execute the rest of the code of the SaveStudent async method.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Environment.CurrentManagedThreadId will return the currently executing unique thread id

Console.WriteLine($"Main Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

var student = new Student()

FirstName = "James",

LastName = "Smith",

Branch = "ETC"

};

//Calling the Async SaveStudent Method

var result = SaveStudent(student);

Console.WriteLine($"Main Method Doing Some Other Task by ThreadId:


{Environment.CurrentManagedThreadId}");

//Let us wait for the SaveStudent method to complete


926

//await result;

Console.WriteLine($"Main Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

private static async Task SaveStudent(Student newStudent)

Console.WriteLine($"SaveStudent Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

using (var context = new EFCoreDbContext())

context.Students.Add(newStudent);

Console.WriteLine($"SaveChangesAsync Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

//Calling the SaveChangesAsync Method, it will release the calling thread

await (context.SaveChangesAsync());

Console.WriteLine($"SaveChangesAsync Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

}
927

Console.WriteLine($"SaveStudent Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

}
Output:

As you can see in the above output, the Main method is executed by one thread, and two
threads execute the SaveStudent method. This is possible because when the
SaveChangesAsync method is called, it releases the calling thread, and when the
SaveChangesAsync method completes its execution, another thread from the thread pool
will come and execute the rest of the code.
Asynchronous Delete in Entity Framework Core
In this case, first, you need to Remove the entity from the context object, which will mark the
entity state as Deleted. When we call the SaveChangesAsync() method, it will remove the
entity from the database asynchronously. For a better understanding, please have a look at
the following example. The following example code is self-explained, so please go through
the comment lines.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static void Main(string[] args)

try

{
928

//Environment.CurrentManagedThreadId will return the currently executing unique thread id

Console.WriteLine($"Main Method Start by ThreadId:


{Environment.CurrentManagedThreadId}");

var deleteAsyncQuery = DeleteStudentAsync(1);

Console.WriteLine($"Main Method Doing Some other task by ThreadId:


{Environment.CurrentManagedThreadId}");

//Wait till the DeleteStudentAsync Method completed its execution

var deleteAsyncResult = deleteAsyncQuery.Result;

Console.WriteLine($"DeleteStudentAsync Method Result: {deleteAsyncResult}");

Console.WriteLine($"Main Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

public static async Task<string> DeleteStudentAsync(int StudentId)

Console.WriteLine($"DeleteStudentAsync Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

using (var context = new EFCoreDbContext())

{
929

Console.WriteLine($"FindAsync Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

//Calling the FindAsync Method, it will release the thread

Student? DeleteStudent = await context.Students.FindAsync(StudentId);

if(DeleteStudent != null )

//Then remove the entity by calling the Remove Method which will mark the Entity State as
Deleted

context.Students.Remove(DeleteStudent);

Console.WriteLine($"SaveChangesAsync Method Started by ThreadId:


{Environment.CurrentManagedThreadId}");

//Calling the SaveChangesAsync Method, it will release the thread

await (context.SaveChangesAsync());

Console.WriteLine($"DeleteStudentAsync Method Completed by ThreadId:


{Environment.CurrentManagedThreadId}");

return "Student Deleted Successfully";

}
Output:
930

Note: If the student you are trying to delete has any foreign key relationship data, it will not
allow you to delete the student. In such cases, first, you need to delete foreign key table
data and then delete the student data. In most real-time applications, we never delete any
data from the database. In most cases, we use some kind of flag, such as IsActive, which
indicates whether the record is active or inactive.
Asynchronous Operation with Cancellation Token in EF Core:
Cancellation is an important feature of asynchronous programming, especially when
working with Entity Framework Core (EF Core), to ensure that long-running database
operations can be canceled when needed. Let us see an example to understand how to use
cancellation with EF Core asynchronous queries:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

// Create a cancellation token source with a timeout of 500 Milliseconds

var cancellationTokenSource = new


CancellationTokenSource(TimeSpan.FromMilliseconds(500));

CancellationToken cancellationToken = cancellationTokenSource.Token;

try

//Creating the Context Object


931

using var context = new EFCoreDbContext();

// Asynchronously retrieve a list of students with a cancellation token

List<Student> students = await context.Students

.Where(s => s.Branch.StartsWith("C")) // Example query

.ToListAsync(cancellationToken);

foreach (var student in students)

Console.WriteLine($"Student ID: {student.StudentId}, Name: {student.FirstName}


{student.LastName}, Branch: {student.Branch}");

Console.Read();

catch (OperationCanceledException)

Console.WriteLine("Query was canceled due to a timeout.");

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
In this code:
932

 We create a CancellationTokenSource with a timeout of 500 Milli Seconds


(TimeSpan.FromMilliseconds(5)) to specify the maximum duration for the
query.
 We obtain a CancellationToken from the CancellationTokenSource. This
token will cancel the query if it exceeds the specified timeout.
 We create an asynchronous query using EF Core’s ToListAsync method,
passing the cancellationToken to the method. This allows us to cancel the
query if it runs longer than the specified timeout.
 If the query completes within the timeout, the list of students is displayed. If
the query is canceled due to the timeout, an OperationCanceledException is
caught, and a corresponding message is displayed.
Advantages and Disadvantages of Asynchronous Programming with EF
Core
Asynchronous programming with Entity Framework Core (EF Core) offers several
advantages and disadvantages, depending on how it’s used and the specific requirements
of your application. Here are the advantages and disadvantages of asynchronous
programming with EF Core:
Advantages of Asynchronous Programming with Entity Framework
Core:
 Improved Scalability: Asynchronous operations free up the thread to handle
other requests while waiting for I/O operations to complete, thus better-using
server resources and handling more requests concurrently.
 Enhanced Responsiveness: In UI applications, asynchronous database
calls prevent the UI from freezing or becoming unresponsive while the
database operation is processed. This is important for maintaining a smooth
user experience, especially in client applications like web and desktop
applications.
 Efficient Resource Utilization: Asynchronous programming leads to more
efficient use of server resources, as threads are not idly waiting for I/O
operations to complete.
Disadvantages of Asynchronous Programming with Entity Framework
Core:
 Complexity: Asynchronous code can be more complex to write and maintain
than synchronous code, especially when handling error cases, cancellation,
and coordination between asynchronous tasks.
 Debugging: Debugging asynchronous code can be challenging, as
breakpoints and debugging tools may not work seamlessly with
asynchronous operations, making it harder to trace the execution flow.
 Deadlocks: Incorrectly written asynchronous code can lead to deadlocks,
especially when mixing synchronous and asynchronous code or not properly
handling synchronization context.
933

Disconnected Entities in Entity Framework Core (EF Core)


In this article, I will discuss Disconnected Entities in Entity Framework Core (EF
Core) with Examples. Please read our previous article, discussing Asynchronous
Programming with Entity Framework Core with Examples. At the end of this article, you
will understand the following pointers:
1. Type of Entities in Entity Framework Core
2. What are Disconnected Entities Entity Framework Core?
3. How to Use Disconnected Entities Entity Framework Core?
4. How to Save a Disconnected Entity in Entity Framework?
5. Example to Understand Disconnected Entity in Entity Framework Core
6. How to Save a New Entity in Entity Framework Core Disconnected
Scenario?
7. How to Update an Existing Entity in Entity Framework Core
Disconnected Scenario?
8. How to Delete a Disconnected Entity in Entity Framework Core?
9. When to use Disconnected Entity in Entity Framework Core?
Type of Entities in Entity Framework Core:
In Entity Framework Core (EF Core), entities can be in one of three states with respect to
tracking:
 Tracked Entities: These entities are actively tracked by the EF Core context.
Any changes to these entities will be detected and persisted in the database
when the context is saved.
 Detached Entities: These entities were previously tracked by the context but
have been detached. This can happen when you explicitly detach an entity,
when the context is disposed, or when the entity was never tracked by the
context in the first place.
 Disconnected Entities: The context did not track these entities from the
beginning. You may create entity objects independently of the context,
manipulate them, and then attach them to the context when ready to save
changes.
What are Disconnected Entities Entity Framework Core (EF Core)?
The Entity Framework Core DbContext instance automatically tracks the entities returned
from the database. The context object also tracks any changes we make to these entities,
and when we call the SaveChanges Method on the context object, the changes are updated
in the database. This is what we have already discussed so far in our previous articles.
In some scenarios, it is also possible that entities are retrieved from the database using one
context object and then updated or saved into the database using a different context object.
In this scenario, the second context object, to update the entities in the database, needs to
know the entity state of the entities, i.e., whether the entities are Added state or existing
entities are in Modified or Deleted state.

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:11 / 03:1710 Sec
934

Disconnected entities in Entity Framework Core refer to entity instances not currently
tracked by any DbContext instance. This scenario is common in Web Applications, Web
APIs, and other distributed systems where an entity is retrieved in one request and then
modified in a subsequent request. Managing disconnected entities properly is important for
ensuring data consistency and integrity.
How to Use Disconnected Entities Entity Framework Core (EF Core)?
Working with disconnected entities is a common scenario when dealing with application
data. Here are the typical steps to work with disconnected entities in EF Core:
 Create the Disconnected Entity: Instantiate an entity object representing
the data you want to work with. You can create these entities using their
parameterless or any other constructor you define.
 Modify the Entity: Make changes to the entity’s properties as needed.
 Attach to Context: When you are ready to save changes to the database,
you need to attach the disconnected entity to the EF Core context. You can
attach the entity to the context using the Attach, Entry, or Add method.
 Save Changes: After attaching the entity to the context and specifying the
desired state (e.g., Added, Modified, Deleted, or Unchanged), you can call
SaveChanges to persist the changes to the database.
This process allows us to work with entity objects outside the context and then bring them
back into the context for saving changes. Remember that when attaching entities, you must
ensure the primary key values are set correctly to match the database records you intend to
update or insert.
How to Save a Disconnected Entity in Entity Framework?
In Entity Framework Core, saving data in the disconnected environment differs from saving
data in the connected environment. In a disconnected environment, the context object (an
instance of the DbContext class) is not aware of the state of the entities, whether the
entities are new or existing. Such entities are called Disconnected Entities. In this case, i.e.,
in the disconnected scenario, first of all, we need to attach the disconnected entities to the
context object with the appropriate Entity State to perform INSERT, UPDATE, or DELETE
operations in the database.
In the Entity Framework Core Disconnected Scenario, it is our key responsibility to figure
out whether the entity is a new one or an existing entity, and based on this, we need to set
the appropriate entity state. The important question that should come to your mind is how
we will determine whether the entity is new or existing. For this, we need to check the key
property value, i.e., the Primary key value of the entity.
If the key property value is greater than zero (in case of Integer column) or Not Null or
Empty (in case of String Column), then it is an existing Entity, and we can set the Entity
State as Modified. On the other hand, if the key value is zero (in case of Integer column) or
Null or Empty (in case of String Column), then it is a new entity, and we need to set the
Entity State as Added.
For a better understanding, please have a look at the below diagram. First, we need to
attach the Entity. While attaching the entity to the context object, we need to check the
primary key column corresponding property value and if the value is greater than 0 (let us
assume it is an integer property). We need to set the Entity State as Modified, and if the
value equals zero, then we need to set the Entity State as Added. Then, we need to call the
SaveChanges method, which will generate either the INSERT or UPDATE SQL Statement
based on the Entity State and execute the SQL Statement in the database.
935

Example to Understand Disconnected Entity in Entity Framework Core:


In Entity Framework Core, working in a disconnected scenario means that the operations
(like adding, modifying, and deleting entities) are being performed outside the context of the
DbContext. Let us understand how to Insert, Update, and Delete Entities in the Entity
Framework Core Disconnected scenario. For this, we are going to use the following Student
Entity.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

{
936

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated SQL Statements

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.
937

How to Save a New Entity in Entity Framework Core Disconnected


Scenario?
Let us see how we can save a new entity in a disconnected scenario. Please modify the
Main method of the Program class as shown below. The following is an example of the
Entity Framework Disconnected Scenario for adding a new entity.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Disconnected entity

Student student = new Student()

FirstName = "Pranaya",
938

LastName = "Rout"

};

using var context = new EFCoreDbContext();

//Setting the Entity State Based on the StudentId Key Property Value

//If StudentId == 0, Means It is a New Entity, So, Set the Entity State as Added

//If StudentId > 0, Means It is an Existing Entity, So, Set the Entity State as Modified

//If StudentId < 0, Invalid ID, so throw an Exception

if(student.StudentId > 0)

//Attaching the Entity to the Context Object with Entity State as Modified

context.Entry(student).State = EntityState.Modified;

else if(student.StudentId == 0)

//Attaching the Entity to the Context Object with Entity State as Added

context.Entry(student).State = EntityState.Added;

else

throw new Exception("InValid Student ID");

Console.WriteLine($"Before SaveChanges Entity State: {context.Entry(student).State}");

//Call SaveChanges to Update the Data in the Database

context.SaveChanges();
939

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
In our example, we have not set the StudentId value while creating the student object, so
the default value is 0. Hence, in this example, it will set the Entity State as Added, and when
we call the SaveChanges method, it will generate and execute the INSERT SQL Statement
in the database. So, when you run the above example code, you will get the following
output.

How to Update an Existing Entity in Entity Framework Core


Disconnected Scenario?
Please modify the Main method of the Program class as shown below. The following is an
example of the Entity Framework Core Disconnected Scenario for updating an Existing
Entity. In the below example, we set the value of the StudentId property, and hence, it will
assign the entity State as the Modified state. And when we run the following application, it
generates and executes the UPDATE SQL Statement. Please ensure the StudentId you
specified here in the Student entity must exist in the database. Else, you will get an
exception.
using EFCoreCodeFirstDemo.Entities;
940

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Disconnected entity

Student student = new Student()

StudentId = 1, //Make Sure StudentId 1 exists in the Database

FirstName = "Pranaya",

LastName = "Rout"

};

using var context = new EFCoreDbContext();

//Setting the Entity State Based on the StudentId Key Property Value

//If StudentId == 0, Means It is a New Entity, So, Set the Entity State as Added

//If StudentId > 0, Means It is an Existing Entity, So, Set the Entity State as Modified

//If StudentId < 0, Invalid ID, so throw an Exception

if(student.StudentId > 0)

//Attaching the Entity to the Context Object with Entity State as Modified
941

context.Entry(student).State = EntityState.Modified;

else if(student.StudentId == 0)

//Attaching the Entity to the Context Object with Entity State as Added

context.Entry(student).State = EntityState.Added;

else

throw new Exception("InValid Student ID");

Console.WriteLine($"Before SaveChanges Entity State: {context.Entry(student).State}");

//Call SaveChanges to Update the Data in the Database

context.SaveChanges();

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
942

Output:

How to Delete a Disconnected Entity in Entity Framework Core?


Deleting a disconnected Entity in Entity Framework Core is very simple. You need to set its
state as Delete using the Entry() method, as shown in the below example. Please make
sure the StudentId that you specified here in the Student entity exists in the database,
otherwise, you will get an exception.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Disconnected entity

Student student = new Student()

StudentId = 1 //Make Sure StudentId 1 exists in the Database

};
943

using var context = new EFCoreDbContext();

//Setting the Entity State as Deleted

context.Entry(student).State = EntityState.Deleted;

Console.WriteLine($"Before SaveChanges Entity State: {context.Entry(student).State}");

//Call SaveChanges to Update the Data in the Database

context.SaveChanges();

Console.WriteLine($"After SaveChanges Entity State: {context.Entry(student).State}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
The student instance contains only the StudentId key property in the above example.
Deleting an entity using the entity framework only requires the key
property. context.Entry(student).State = EntityState.Deleted attaches an entity to the
context object and sets its state to Deleted. When we call the SaveChanges method on the
context object, it generates and executes the DELETE SQL Statement in the database. So,
when you run the above example, you will get the following output.
944

When to use Disconnected Entity in Entity Framework Core (EF Core)?


Disconnected entities in Entity Framework Core are often used in scenarios where the
DbContext is not continuously available throughout the life cycle of an entity. Disconnected
scenarios are common in applications like Web applications, Web APIs, and other multi-tier
architectures. Here are some situations where you might use disconnected entities:
 Web Applications and APIs: HTTP requests are typically short-lived in Web
Applications and APIs. The DbContext is often disposed at the end of each
request. In this case, disconnected entities are common as the entities may
be passed from the client, modified, and then saved back in a new DbContext
instance.
 Multi-tier Architectures: In a multi-tier architecture where the presentation
layer is separated from the data access layer, entities are often passed
between layers and may not always be attached to a DbContext.
 Scalability: Disconnected entities can help in scenarios where scalability is
important. Since the context is not kept alive, resources like database
connections are released quickly, which can be beneficial in high-load
scenarios.
 Long-Running Processes: For long-running processes or desktop
applications where keeping a DbContext open for the duration of the
operation might not be feasible, working with disconnected entities becomes
necessary.
 Batch Processing: When working with batch processing that handles a large
amount of data, you might work with disconnected entities to manage
resources effectively.
Considerations When Working With Disconnected Entities:
 Tracking Changes: Since the DbContext is not tracking changes to
disconnected entities, you must manually manage the entities’ state.
 Concurrency: Handling concurrency can be more complex in disconnected
scenarios as the DbContext might not have the original values of the entity.
945

Disconnected Entity Graph in Entity Framework Core (EF Core)


In this article, I will discuss Disconnected Entity Graph in Entity Framework Core (EF
Core) with Examples. Please read our previous article discussing Disconnected Entities
in Entity Framework Core with Examples.
1. What is a Disconnected Entity Graph in Entity Framework Core
2. How to Attach a Disconnected Entity Graph in Entity Framework Core?
3. Different Approaches to Attach a Disconnected Entity Graph in EF
Core?
4. Attach() Method in Entity Framework Core
5. Entry() Method in Entity Framework Core
6. Add() Method in Entity Framework Core
7. ChangeTracker.TrackGraph() in Entity Framework Core
8. When to use Disconnected Entity Graph in Entity Framework Core?
What is a Disconnected Entity Graph in Entity Framework Core?
An Entity Graph in EF Core refers to a graph-like structure representing a set of related
entities and their relationships within your application’s data model. That means an entity
graph in Entity Framework Core refers to an entity and its related data. Entity Graphs
represent and manage complex data relationships in your application.
Handling disconnected entity graphs in Entity Framework Core involves working with
complex objects that include related entities. In a disconnected scenario, such as a Web
application or an API, the entities are retrieved in one operation (e.g., HTTP request) and
then sent back to the server in a modified state in a subsequent operation. Managing these
disconnected entity graphs correctly is essential for maintaining data integrity and correctly
persisting the changes to the database.
How to Attach a Disconnected Entity Graph in Entity Framework Core
In Entity Framework Core (EF Core), we can attach a disconnected entity graph to the
context object using the Attach, Add, and Entry methods. Let us understand How to
Attach a Disconnected Entity Graph in Entity Framework Core with an Example. Before
that, let us first understand what is an Entity Graph in Entity Framework Core. Let us first
create the Parent and Child Entities:

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:27 / 03:1710 Sec
Student.cs
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }


946

public string FirstName { get; set; }

public string LastName { get; set; }

public int StandardId { get; set; }

public virtual Standard Standard { get; set; }

public virtual StudentAddress StudentAddress { get; set; }

}
Standard.cs
namespace EFCoreCodeFirstDemo.Entities

public class Standard

public int StandardId { get; set; }

public string StandardName { get; set; }

public string Description { get; set; }

public ICollection<Student> Students { get; set; }

}
StudentAddress.cs
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public class StudentAddress

[Key]
947

public int StudentId { get; set; } // PK and FK

public string Address1 { get; set; }

public string Address2 { get; set; }

public Student Student { get; set; }

}
In this case, the Student Entity is the Main or Parent entity. Standard, StudentAddress, and
Courses are the Child Entities or related entities, and combined together, we can say it’s an
Entity Graph. Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

public DbSet<Standard> Standards { get; set; }


948

public DbSet<StudentAddress> StudentAddresses { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, then you will see the following:

Different Approaches to Attach a Disconnected Entity Graph in Entity


Framework Core?
Entity Framework Core provides the following methods, which not only attach an entity to
the context object but also change the EntityState of each entity in a disconnected entity
graph:
 Attach()
 Entry()
 Add()
949

Let’s see how the above methods change the EntityState of each entity in a disconnected
entity graph in Entity Framework Core. Before Proceeding further, please Insert the
following record into the Standards database table:
INSERT INTO Standards Values ('STD1', 'STD1 Description');
Attach() Method in Entity Framework Core (EF Core):
In Entity Framework Core, the Attach method attaches an entity to the DbContext instance
so that the context object tracks it. Attaching an entity means EF Core is aware of its
existence and will start tracking changes to that entity. This is commonly used when
working with entities disconnected from the context, such as entities created outside or
fetched from another context object.
The DbContext.Attach() and DbSet.Attach() method attaches the specified disconnected
entity graph and starts tracking it. The Attach Method attaches an entire Entity Graph
(Parent and Child Entities) to the context object with the specified state to the parent entity.
Also, it sets different Entity State to related entities, i.e., to the child entities.
The Attach() method sets Added EntityState to the root entity (in this case, Student)
irrespective of whether it contains the Key value. A child entity containing the key value will
be marked as Unchanged. Otherwise, it will be marked as Added. The output of the above
example shows that the Student entity has an Added Entity State, the child entities with
non-empty key values have an Unchanged Entity State, and the ones with empty key
values have an Added state. The following table lists the behavior of the Attach() method
when setting a different EntityState to a disconnected entity graph.

To better understand how to use the Attach method to attach an Entity Graph to the context
object, please modify the Program class as follows. The following example code is self-
explained, so please go through the comment lines.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program


950

static async Task Main(string[] args)

try

//Creating the Disconnected Entity Graph

//Student Entity Graph (Student plus Standard, and StudentAddress)

//Student is the Main Entity

//Standard, and StudentAddress are the Child Entities

var student = new Student()

//Root Entity with Empty key

FirstName = "Pranaya",

LastName = "Rout",

StandardId = 1,

//Make Sure the StandardId with Value 1 Exists in the Database, else you will get Exception

Standard = new Standard() //Child Entity with key value

StandardId = 1,

StandardName = "STD1",

Description = "STD1 Description"

},

StudentAddress = new StudentAddress() //Child Entity with Empty Key

{
951

Address1 = "Address Line1",

Address2 = "Address Line2"

};

//Creating an Instance of the Context class

using var context = new EFCoreDbContext();

//Attaching the Disconnected Student Entity Graph to the Context Object

context.Attach(student).State = EntityState.Added;

//Checking the Entity State of Each Entity of student Entity Graph

foreach (var entity in context.ChangeTracker.Entries())

Console.WriteLine($"Entity: {entity.Entity.GetType().Name}, State: {entity.State} ");

// Save changes to persist the changes to the database

context.SaveChanges();

Console.WriteLine("Entity Graph Saved Successfully");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
952

}
Output:

In the above example, the student is an instance of the Student entity graph, which
includes references to StudentAddress and Standard
entities. context.Attach(student).State = EntityState.Added; attaches the student entity
graph to a context object and sets Added state.
Entry() Method in Entity Framework Core:
In Entity Framework Core (EF Core), the Entry method is used to get an EntityEntry object
representing an entity that the DbContext tracks. The EntityEntry object provides various
methods and properties that allow us to inspect and manipulate the state of the entity being
tracked, such as changing property values, setting the entity state, and more.
Consider the following example for a better understanding. In this case, only the root entity
will be added, updated, or modified in the database. It will not affect the Child Entities.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Creating the Disconnected Entity Graph

//Student Entity Graph (Student plus Standard, and StudentAddress)

//Student is the Main Entity

//Standard, and StudentAddress are the Child Entities


953

var student = new Student()

//Root Entity with Empty key

FirstName = "Pranaya",

LastName = "Rout",

StandardId = 1,

Standard = new Standard() //Child Entity with key value

StandardId = 1,

StandardName = "STD1",

Description = "STD1 Description"

},

StudentAddress = new StudentAddress() //Child Entity with Empty Key

Address1 = "Address Line1",

Address2 = "Address Line2"

};

//Creating an Instance of the Context class

using var context = new EFCoreDbContext();

//Attaching the Disconnected Student Entity Graph to the Context Object

context.Entry(student).State = EntityState.Added;

//Checking the Entity State of Each Entity of student Entity Graph

foreach (var entity in context.ChangeTracker.Entries())


954

Console.WriteLine($"Entity: {entity.Entity.GetType().Name}, State: {entity.State} ");

// Save changes to persist the changes to the database

context.SaveChanges();

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output: Entity: Student, State: Added
In the above example, context.Entry(student).State = EntityState.Added; attaches an
entity to a context and applies the specified EntityState (in this case, Added) to the root
entity, irrespective of whether it contains a Key property value. It ignores all the child entities
in a graph and does not attach or set their EntityState. The following table lists different
behaviors of the DbContext.Entry() method.

Add() Method in Entity Framework Core (EF Core):


The DbContext.Add and DbSet.Add methods attach an entity graph to a context and set
Added EntityState to a root and child entity regardless of whether a key value exists. This
means that the entity is considered new and should be inserted into the database when we
955

call the SaveChanges method. The Add method is commonly used when creating and
inserting a new record into the database. In this case, providing explicit values for the
Identity column is restricted. You will get a Run Time Exception if you provide the Identity
Column values. For a better understanding, please have a look at the following example:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Creating the Disconnected Entity Graph

//Student Entity Graph (Student plus Standard, and StudentAddress)

//Student is the Main Entity

//Standard, and StudentAddress are the Child Entities

var student = new Student()

//Root Entity with key

//StudentId = 1, //It is Identity, so you cannot set Explicit Value

FirstName = "Pranaya",

LastName = "Rout",

StandardId = 1,

Standard = new Standard() //Child Entity with key value

{
956

// StandardId = 1, //It is Identity, so you cannot set Explicit Value

StandardName = "STD1",

Description = "STD1 Description"

},

StudentAddress = new StudentAddress() //Child Entity with Empty Key

Address1 = "Address Line1",

Address2 = "Address Line2"

};

//Creating an Instance of the Context class

using var context = new EFCoreDbContext();

//Attaching the Disconnected Student Entity Graph to the Context Object

context.Students.Add(student);

//Checking the Entity State of Each Entity of student Entity Graph

foreach (var entity in context.ChangeTracker.Entries())

Console.WriteLine($"Entity: {entity.Entity.GetType().Name}, State: {entity.State} ");

// Save changes to persist the changes to the database

context.SaveChanges();

Console.WriteLine("Entity Graph Added");

Console.Read();

}
957

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

The following diagram lists the possible EntityState of each entity in a graph using the
DbContext.Add or DbSet.Add methods.

ChangeTracker.TrackGraph() in Entity Framework Core (EF Core)


The TrackGraph (a static method in the ChangeTracker class) method allows us to
manually set the entity state of an entire graph or set the state based on some condition,
which is then tracked by the context object. This is useful when working with a complex
object graph and managing the state of entities within that graph, such as marking some
entities as added, modified, or deleted and ensuring that EF Core tracks those changes
correctly. The following is the signature of the TrackGraph method.
Signature: public virtual void TrackGraph(object rootEntity,
Action<EntityEntryGraphNode> callback)
The ChangeTracker TrackGraph() method tracks an entity and any reachable entities by
traversing its navigation properties. The specified callback is called for each reachable
entity, and an appropriate EntityState is set for each entity. The callback function allows us
to implement a custom logic to set the appropriate state. If no state is set, the entity remains
untracked. The following example demonstrates the ChangeTracker TrackGraph method.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo
958

public class Program

static async Task Main(string[] args)

try

//Creating the Disconnected Entity Graph

//Student Entity Graph (Student plus Standard, and StudentAddress)

//Student is the Main Entity

//Standard, and StudentAddress are the Child Entities

var student = new Student()

//Root Entity without key

FirstName = "Pranaya",

LastName = "Rout",

StandardId = 1,

Standard = new Standard() //Child Entity with key value

StandardId = 1,

StandardName = "STD1",

Description = "STD1 Description"

},

StudentAddress = new StudentAddress() //Child Entity with Empty Key


959

Address1 = "Address Line1",

Address2 = "Address Line2"

};

//Creating an Instance of the Context class

using var context = new EFCoreDbContext();

context.ChangeTracker.TrackGraph(student, e =>

if (e.Entry.IsKeySet)

//If Key is Available set the State as Unchanged or Modified as Per Your Requirement

e.Entry.State = EntityState.Unchanged;

else

// If Key is not Available set the State as Added

e.Entry.State = EntityState.Added;

});

//Checking the Entity State of Each Entity of student Entity Graph

foreach (var entity in context.ChangeTracker.Entries())

Console.WriteLine($"Entity: {entity.Entity.GetType().Name}, State: {entity.State} ");


960

// Save changes to persist the changes to the database

context.SaveChanges();

Console.WriteLine("Entity Graph Saved..");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

In the above example, the TrackGraph() method sets the state for each entity of the Student
entity graph. The first parameter is an entity graph, and the second parameter is a function
that sets the state of each entity. We used a lambda expression to set the Unchanged state
for entities that have valid key values and the Added state for entities that have empty key
values. The IsKeySet becomes true when an entity has a valid key property value. So, we
can use the ChangeTracker TrackGraph() method to set different States for each entity in a
graph.
Another Example to Understand TrackGraph() Method:
Suppose we want to set the Root Entity State as Modified when the Key Property Value is
available; if the key value is unavailable, we need to set the state as Added. But for the
Child Entity, we need to set the Entity State as UnChanged when the Key Value is
available, the Entity State should be Added. To achieve this, please modify the Main
Method of the Program class as follows:
using EFCoreCodeFirstDemo.Entities;
961

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Creating the Disconnected Entity Graph

//Student Entity Graph (Student plus Standard, and StudentAddress)

//Student is the Main Entity

//Standard, and StudentAddress are the Child Entities

var student = new Student()

//Root Entity without key

FirstName = "Hina",

LastName = "Sharma",

StandardId = 1,

Standard = new Standard() //Child Entity with key value

StandardId = 1,

StandardName = "STD1",

Description = "STD1 Description"


962

},

StudentAddress = new StudentAddress() //Child Entity with Empty Key

Address1 = "Address Line1",

Address2 = "Address Line2"

};

using var context = new EFCoreDbContext();

// Use TrackGraph to track the entire graph

context.ChangeTracker.TrackGraph(student, nodeEntry =>

// Customize tracking behavior for each entity

//Setting the Root Entity, i.e., Student

if (nodeEntry.Entry.Entity is Student std)

if (std.StudentId > 0)

nodeEntry.Entry.State = EntityState.Modified;

else

nodeEntry.Entry.State = EntityState.Added;

}
963

//Setting the Child Entity i.e. Standard and StudentAddress

else

if (nodeEntry.Entry.IsKeySet)

//If Key is Available set the State as Unchanged as Per Your Requirement

nodeEntry.Entry.State = EntityState.Unchanged;

else

// If Key is not Available set the State as Added as Per Your Requirement

nodeEntry.Entry.State = EntityState.Added;

});

//Checking the Entity State of Each Entity of student Entity Graph

foreach (var entity in context.ChangeTracker.Entries())

Console.WriteLine($"Entity: {entity.Entity.GetType().Name}, State: {entity.State} ");

// Save changes to persist the changes to the database

context.SaveChanges();

Console.WriteLine("Entity Graph Saved..");

Console.Read();
964

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

We create a graph of entities starting with the Student entity as the root, containing related
Standard and StudentAddress entities. We use the ChangeTracker TrackGraph method to
track the entire entity graph. Inside the delegate provided to TrackGraph, we customize the
tracking behavior for each entity based on our requirements.
When to use Disconnected Entity Graph in Entity Framework Core?
Disconnected Entity Graphs in Entity Framework Core are used in scenarios where you
want to work with entity data outside of the context in which it was originally retrieved, and
later, you want to reconnect those entities to a new context to perform database operations
like updating, inserting, or deleting records.
This approach is common in applications where entities must be passed between different
layers, such as a user interface, business logic, and data access layers, or when entities
must be serialized and sent across a network. Disconnected entity graphs are useful in the
following scenarios:
 Web Applications: In web applications, entities are often retrieved from the
database within an HTTP request, but they must be passed to a different
layer for processing. Later, when the HTTP request is complete, the entities
may need to be updated or saved back to the database. Disconnected
entities allow you to work with the data across different parts of your
application.
 Batch Processing: In batch processing scenarios, you might retrieve many
entities from the database, process them in a batch job, and then save the
changes. Disconnected entities allow you to efficiently work with these
entities, even if they are not continuously connected to a context.
 Client-Server Applications: In client-server applications, entities may be
sent from a client to a server for processing and then returned to the client.
965

Disconnected entities allow you to serialize and deserialize the data without
maintaining a continuous database connection on the client side.
 Caching: You can use disconnected entities to store a data snapshot in
memory for caching purposes. When the data is requested again, you can
reattach the entities to a new context to apply updates or insert new data.

Stored Procedures in Entity Framework Core (EF Core)


In this article, I will discuss Stored Procedures in Entity Framework Core (EF Core) with
Examples. Please read our previous article discussing Disconnected Entity Graph in
Entity Framework Core with Examples. At the end of this article, you will understand How
to Perform database CRUD Operations using Stored Procedure in Entity Framework core.
Stored Procedures in Entity Framework Core:
Entity Framework Core (EF Core) is an Object-Relational Mapping (ORM) framework
for .NET that allows us to work with databases using .NET objects. While EF Core primarily
focuses on LINQ-based query capabilities and entity modeling, it also supports stored
procedures.
Stored Procedures in Entity Framework Core provide a way to execute predefined SQL
logic on the database server. This can benefit performance, especially for complex queries.
How to Call Stored Procedure in Entity Framework Core
Calling a Stored Procedure in Entity Framework Core can be done in a few different ways
depending on whether your stored procedure returns data, performs an INSERT, UPDATE,
DELETE, or executes a command. Below are methods to handle each of these scenarios:

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:27 / 03:1710 Sec
Executing a Stored Procedure that Returns Data:
If your stored procedure returns data, you can execute the Stored Procedure using
the FromSqlRaw or FromSqlInterpolated methods and map the results to entity types.
Suppose you have a stored procedure named GetCustomers that returns a set of
customers. You can call it like this:
var customers = context.Customers.FromSqlRaw("EXEC GetCustomers").ToList();

//Using interpolated syntax (which is safer from SQL injection):

var customers = context.Customers.FromSqlInterpolated($"EXEC GetCustomers").ToList();


Executing a Stored Procedure for INSERT, UPDATE, DELETE
For stored procedures that perform INSERT, UPDATE, DELETE, or other non-query
operations, you can use the ExecuteSqlRaw or ExecuteSqlInterpolated method of the
database object. Suppose you have a stored procedure named UpdateCustomer that
updates a customer’s data:
context.Database.ExecuteSqlRaw("EXEC UpdateCustomer @CustomerId, @Name",
parameters: new[] { customerIdParameter, nameParameter });
966

//Using interpolated syntax:

context.Database.ExecuteSqlInterpolated($"EXEC UpdateCustomer {customerId},


{name}");
Stored Procedure with Output Parameters
If your stored procedure has output parameters, you must set Parameter Direction as
Output and execute the Stored Procedure as follows.
//Input Parameter

var customerIdParameter = new SqlParameter("CustomerId", 1);

//Output Parameter

var customerNameParameter = new SqlParameter

ParameterName = "CustomerName",

SqlDbType = SqlDbType.VarChar,

Direction = ParameterDirection.Output,

Size = 50

};

context.Database.ExecuteSqlRaw("EXEC GetCustomerName @CustomerId,


@CustomerName OUTPUT", customerIdParameter, customerNameParameter);

var customerName = customerNameParameter.Value.ToString();


Key Points To Remember While Working With EF Core Stored
Procedure:
 SQL Injection: When using FromSqlRaw or ExecuteSqlRaw, be careful
about SQL injection risks. Prefer FromSqlInterpolated and
ExecuteSqlInterpolated for parameterized queries.
 Mapping Results: The column names returned by the stored procedure must
match the property names of the entity type for correct mapping.
 Tracking: Queries using FromSqlRaw or FromSqlInterpolated that return
entity types are tracked by default, meaning EF Core will try to keep track of
changes to those entities. If you don’t need this, you can use AsNoTracking.
 No Mapping to Entities: If the results don’t map to entities, you can use ad-
hoc types (DTOs) to capture the result set.
967

Example to Understand How to Call Stored Procedure in Entity


Framework Core:
Let us proceed and understand this step by step. First, create the following Student Entity in
our project.
namespace EFCoreCodeFirstDemo.Entities

public class Student

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Branch { get; set; }

public string Gender { get; set; }

}
Next, modify the Context class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated SQL Statements

//optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
968

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, the database should be created with the Students database table as shown in the
below image:
969

Creating Stored Procedures:


Create the following stored procedures to perform CRUD Operations with the Students
database table.
Stored Procedure for Inserting a Student:
The following Stored Procedure will take the First Name, Last Name, Gender, and Branch
as input parameters and the Student ID as an output parameter, then INSERT the student
data into the Student database table. Then, it will return the newly inserted StudentId using
the SCOPE_IDENTITY() function via the output parameter.
-- Insert Student Stored Procedure

CREATE PROCEDURE spInsertStudent

@FirstName VARCHAR(100),

@LastName VARCHAR(100),

@Branch VARCHAR(100),

@Gender VARCHAR(100),

@StudentId int OUTPUT

AS

BEGIN

INSERT INTO Students(FirstName ,LastName, Branch, Gender)

VALUES(@FirstName, @LastName, @Branch, @Gender);

SELECT @StudentId = SCOPE_IDENTITY()

END
970

Stored Procedure for Updating an Existing Student:


The following Stored Procedure will take the StudentId, First Name, Last Name, Gender,
and Branch as input parameters and then update the First Name, Last Name, Gender, and
Branch data into the Student database table based on the StudentId. This method does not
return anything.
-- Update Student Stored Procedure

CREATE PROCEDURE spUpdateStudent

@StudentId INT,

@FirstName VARCHAR(100),

@LastName VARCHAR(100),

@Branch VARCHAR(100),

@Gender VARCHAR(100)

AS

BEGIN

UPDATE Students

SET FirstName = @FirstName,

LastName = @LastName,

Branch = @Branch,

Gender = @Gender

WHERE StudentId = @StudentId;

END
Stored Procedure for Delete an Existing Student:
The following Stored Procedure is used to Delete an existing Student from the Student
Database Table based on the StudentId.
-- Delete Student Stored Procedure

CREATE PROCEDURE spDeleteStudent

@StudentId int

AS
971

BEGIN

DELETE FROM Students WHERE StudentId = @StudentId

END
Stored Procedure to Fetch All Students:
The following Stored Procedure fetches all Student data from the Student Database Table.
-- Get All Student Stored Procedure

CREATE PROCEDURE spGetAllStudents

AS

BEGIN

SELECT StudentId, FirstName, LastName, Branch, Gender

FROM Students;

END
Stored Procedure to Fetch Student by Student ID:
The following Stored Procedure is used to fetch a particular Student data from the Student
Database Table based on the StudentId.
-- Get Student by Student Id Stored Procedure

CREATE PROCEDURE spGetStudentByStudentId

@StudentId INT

AS

BEGIN

SELECT StudentId, FirstName, LastName, Branch, Gender

FROM Students

WHERE StudentId = @StudentId;

END
How to Call Stored Procedure in Entity Framework Core?
To Call a Stored Procedure in Entity Framework Core, we need to use the following two
methods:
972

 ExecuteSqlRaw/ExecuteSqlInterpolated: In Entity Framework Core (EF


Core), the ExecuteSqlRaw or ExecuteSqlInterpolated methods executes raw
SQL queries or Stored Procedure against the database. The ExecuteSqlRaw
or ExecuteSqlInterpolated Methods are typically used for executing SQL
commands or Stored Procedures that modify the database or for executing
scalar queries that return a single value.
 FromSqlRaw/FromSqlInterpolated: In Entity Framework Core (EF Core),
the FromSqlRaw or FromSqlInterpolated methods execute Raw-SQL Queries
and Stored Procedure and map the retrieved data to entity objects. This can
be useful when we need to perform complex queries or access database
features that are not easily expressible using standard LINQ-to-Entities
queries.
Example to Understand How to Call Stored Procedure with EF Core:
In Entity Framework Core (EF Core), we can work with stored procedures to perform CRUD
(Create, Read, Update, Delete) operations on our database. Let us see how to perform
these operations using stored procedures in EF Core. For a better understanding, please
modify the Program class as follows. In the example below, we use ExecuteSqlRaw and
FromSqlRaw Methods to call the Stored Procedures using EF Core.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.Data.SqlClient;

using Microsoft.EntityFrameworkCore;

using System.Data;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

Student student1 = new Student()

FirstName = "Pranaya",
973

LastName = "Rout",

Branch = "CSE",

Gender = "Male"

};

Student student2 = new Student()

FirstName = "Hina",

LastName = "Sharma",

Branch = "CSE",

Gender = "Female"

};

//Call the AddStudent Method to add a new Student into the Database

int Id1 = AddStudent(student1);

Console.WriteLine($"Newly Added Student Id: {Id1}");

int Id2 = AddStudent(student2);

Console.WriteLine($"Newly Added Student Id: {Id2}");

//Call the GetStudentById Method to Retrieve a Single Student from the Database

var student = GetStudentById(1);

Console.WriteLine("\nGetStudentById: 1");

Console.WriteLine($"Id:{student?.StudentId}, Name: {student?.FirstName}


{student?.LastName}, Branch: {student?.Branch}, Gender:{student?.Gender}");

//Call the GetAllStudents Method to Retrieve All Students from the Database

List<Student> students = GetAllStudents() ?? new List<Student>();

Console.WriteLine("\nGetAllStudents");
974

foreach (var std in students)

Console.WriteLine($"Id:{student?.StudentId}, Name: {student?.FirstName}


{student?.LastName}, Branch: {student?.Branch}, Gender:{student?.Gender}");

//Let us Update the Student Whose Id = 1 i.e.student object

if (student != null)

student.FirstName = "Prateek";

student.LastName = "Sahoo";

//Call the UpdateStudent Method to Update an Existing Student in the Database

UpdateStudent(student);

student = GetStudentById(student.StudentId);

Console.WriteLine("After Updation");

Console.WriteLine($"Id:{student?.StudentId}, Name: {student?.FirstName}


{student?.LastName}, Branch: {student?.Branch}, Gender:{student?.Gender}");

//Let us Delete the Student Whose Id = 1 i.e. student object

if (student != null)

DeleteStudent(student.StudentId);

Console.Read();

}
975

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

//The following Method is going to add a New Student into the database

//And return the new StudentId

public static int AddStudent(Student student)

int result = 0;

try

using var context = new EFCoreDbContext();

var FirstNameParam = new SqlParameter("@FirstName", SqlDbType.NVarChar)

Value = student.FirstName

};

var LastNameParam = new SqlParameter("@LastName", SqlDbType.NVarChar)

Value = student.LastName

};

var BranchParam = new SqlParameter("@Branch", SqlDbType.NVarChar)

Value = student.Branch
976

};

var GenderParam = new SqlParameter("@Gender", SqlDbType.NVarChar)

Value = student.Gender

};

var StudentIdOutParam = new SqlParameter("@StudentId", SqlDbType.Int)

Direction = ParameterDirection.Output

};

int NumberOfRowsAffected = context.Database.ExecuteSqlRaw("EXEC spInsertStudent


@FirstName, @LastName, @Branch, @Gender, @StudentId OUTPUT",

FirstNameParam, LastNameParam, BranchParam, GenderParam, StudentIdOutParam);

if (NumberOfRowsAffected > 0)

// Retrieve the value of the OUT parameter

result = (int) StudentIdOutParam.Value;

catch (Exception ex)

Console.WriteLine($"AddStudent: Error Occurred: {ex.Message}");

return result;

}
977

//The following Method is going to return a Single Student from the database based on
Student Id

//And return the new StudentId

public static Student? GetStudentById(int StudentId)

Student? student = null;

try

using var context = new EFCoreDbContext();

var StudentIdParam = new SqlParameter("@StudentId", SqlDbType.Int)

Value = StudentId

};

//Returning A Single Student Entity

//Execute the non-composable part of your query first using FromSqlRaw or SqlQuery, and

//materialize the results by calling ToList, ToArray, or AsEnumerable to ensure the data is
retrieved from the database

var result = context.Students

.FromSqlRaw("EXEC spGetStudentByStudentId @StudentId", StudentIdParam)

.ToList(); //Materialize the result

if(result.Count > 0)

student = result.FirstOrDefault();

}
978

catch (Exception ex)

Console.WriteLine($"GetStudentById: Error Occurred: {ex.Message}");

return student;

//The following Method is going to return all Students from the database

public static List<Student>? GetAllStudents()

List<Student>? students = new List<Student>();

try

using var context = new EFCoreDbContext();

//Returning All Students

students = context.Students

.FromSqlRaw("EXEC spGetAllStudents")

.ToList();

catch (Exception ex)

Console.WriteLine($"GetAllStudents: Error Occurred: {ex.Message}");

return students;
979

//The following Method is going to Update an Existing Student into the database

public static void UpdateStudent(Student student)

try

using var context = new EFCoreDbContext();

var StudentIdParam = new SqlParameter("@StudentId", SqlDbType.Int)

Value = student.StudentId

};

var FirstNameParam = new SqlParameter("@FirstName", SqlDbType.NVarChar)

Value = student.FirstName

};

var LastNameParam = new SqlParameter("@LastName", SqlDbType.NVarChar)

Value = student.LastName

};

var BranchParam = new SqlParameter("@Branch", SqlDbType.NVarChar)

Value = student.Branch

};

var GenderParam = new SqlParameter("@Gender", SqlDbType.NVarChar)


980

Value = student.Gender

};

//result: Returns the number of rows affected

var result = context.Database.ExecuteSqlRaw("EXEC spUpdateStudent @StudentId,


@FirstName , @LastName, @Branch, @Gender",

StudentIdParam, FirstNameParam, LastNameParam, BranchParam, GenderParam);

if (result > 0)

Console.WriteLine($"\nEntity with StudentId: {student.StudentId} Updated in the


Database");

else

Console.WriteLine($"\nEntity with StudentId: {student.StudentId} Not Updated in the


Database");

catch (Exception ex)

Console.WriteLine($"AddStudent: Error Occurred: {ex.Message}");

//The following Method is going to Delete an Existing Student from the database based on
Student Id
981

//And return the new StudentId

public static Student? DeleteStudent(int StudentId)

Student? student = null;

try

using var context = new EFCoreDbContext();

var StudentIdParam = new SqlParameter("@StudentId", SqlDbType.Int)

Value = StudentId

};

//Deleting an Existing Student Entity

//result: Returns the number of rows affected

var result = context.Database.ExecuteSqlRaw("EXEC spDeleteStudent @StudentId",


StudentIdParam);

if(result > 0)

Console.WriteLine($"\nEntity with StudentId: {StudentId} Deleted from the Database");

else

Console.WriteLine($"\nEntity with StudentId: {StudentId} Not Deleted from the Database");

}
982

catch (Exception ex)

Console.WriteLine($"DeleteStudent: Error Occurred: {ex.Message}");

return student;

}
Output:

ExecuteSqlRaw vs. ExecuteSqlInterpolated in EF Core


Entity Framework Core (EF Core) has two common methods for executing raw SQL
queries: ExecuteSqlRaw and ExecuteSqlInterpolated. Both methods allow us to execute
raw SQL commands directly against the database but differ in their handling of parameters.
ExecuteSqlRaw:
 Usage: This method allows you to execute a raw SQL query by passing the
query string and parameters separately. It’s similar to the traditional
SqlCommand execution in ADO.NET.
 Parameters: You need to pass the parameters explicitly. This means the
query string contains placeholders for parameters, and you must provide the
parameter values separately.
 Security: Since the parameters are passed separately from the query string,
it helps prevent SQL injection attacks. However, it’s still important to be
cautious and avoid concatenating user inputs directly into the query string.
983

ExecuteSqlInterpolated:
 Usage: This method allows you to execute a SQL query using interpolated
string syntax, which makes the code more readable and concise.
 Parameters: It supports C# interpolated strings, where you can embed the
parameters directly into the SQL query string. EF Core will automatically
parameterize these values to prevent SQL injection.
 Security: It’s generally safe from SQL injection attacks as EF Core converts
the interpolated values into parameters.
Choosing Between ExecuteSqlRaw and ExecuteSqlInterpolated:
 Readability: ExecuteSqlInterpolated often leads to more readable code due
to using interpolated strings.
 Safety: Both methods are safe from SQL injection when used properly.
However, ExecuteSqlInterpolated might be more prone to misuse if
developers aren’t careful about directly embedding user input into the query
string.
 Preference: The choice can also depend on personal or team preference
and coding standards.
FromSqlRaw vs. FromSqlInterpolated in EF Core
In Entity Framework Core (EF Core), FromSqlRaw and FromSqlInterpolated are methods
used to execute raw SQL queries and map the results to entities or other types. Both serve
a similar purpose but differ in handling SQL queries and parameters.
FromSqlRaw:
 Usage: This method is used to execute raw SQL queries. You must pass the
SQL query as a string and any parameters as separate arguments.
 Parameters: Parameters are passed explicitly. The query string contains
placeholders (like {0}, {1}, etc.), and the parameters are provided as
additional arguments.
 Security: FromSqlRaw is safe from SQL injection if parameters are used
correctly and not concatenated into the query string. Always use placeholders
for parameters instead of concatenating or interpolating user input directly
into the SQL string.
FromSqlInterpolated:
 Usage: This method allows us to write SQL queries using interpolated string
syntax, making the code more readable.
 Parameters: It supports C# interpolated strings, enabling you to embed
parameters directly within the SQL query string. EF Core automatically
parameterizes these values to prevent SQL injection.
 Security: FromSqlInterpolated is designed to be safe from SQL injection
attacks as it converts interpolated values into parameters.
Choosing Between FromSqlRaw and FromSqlInterpolated:
 Readability: FromSqlInterpolated uses interpolated strings, which can be
more readable and concise. FromSqlRaw requires explicit parameter
passing, which may be less concise but clearer in some cases.
 Safety: Both methods are safe from SQL injection when used correctly.
However, FromSqlInterpolated might be easier to misuse if developers
embed user inputs directly into the SQL string.
984

 Use Case: The choice between these methods often comes down to
personal preference, coding standards, and the specific context in which they
are used.
Example Using ExecuteSqlInterpolated and FromSqlInterpolated
Methods in EF Core
In our previous example, we used the ExecuteSqlRaw and FromSqlRaw methods to call the
Stored Procedures. Let us rewrite the same example using ExecuteSqlInterpolated and
FromSqlInterpolated Methods in EF Core to call the Stored Procedures. For a better
understanding, please look at the below example.
using EFCoreCodeFirstDemo.Entities;

using Microsoft.Data.SqlClient;

using Microsoft.EntityFrameworkCore;

using System.Data;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

Student student1 = new Student()

FirstName = "Pranaya",

LastName = "Rout",

Branch = "CSE",

Gender = "Male"

};
985

Student student2 = new Student()

FirstName = "Hina",

LastName = "Sharma",

Branch = "CSE",

Gender = "Female"

};

//Call the AddStudent Method to add a new Student into the Database

int Id1 = AddStudent(student1);

Console.WriteLine($"Newly Added Student Id: {Id1}");

int Id2 = AddStudent(student2);

Console.WriteLine($"Newly Added Student Id: {Id2}");

//Call the GetStudentById Method to Retrieve a Single Student from the Database

var student = GetStudentById(1);

Console.WriteLine("\nGetStudentById: 1");

Console.WriteLine($"Id:{student?.StudentId}, Name: {student?.FirstName}


{student?.LastName}, Branch: {student?.Branch}, Gender:{student?.Gender}");

//Call the GetAllStudents Method to Retrieve All Students from the Database

List<Student> students = GetAllStudents() ?? new List<Student>();

Console.WriteLine("\nGetAllStudents");

foreach (var std in students)

Console.WriteLine($"Id:{student?.StudentId}, Name: {student?.FirstName}


{student?.LastName}, Branch: {student?.Branch}, Gender:{student?.Gender}");
986

//Let us Update the Student Whose Id = 1 i.e.student object

if (student != null)

student.FirstName = "Prateek";

student.LastName = "Sahoo";

//Call the UpdateStudent Method to Update an Existing Student in the Database

UpdateStudent(student);

student = GetStudentById(student.StudentId);

Console.WriteLine("After Updation");

Console.WriteLine($"Id:{student?.StudentId}, Name: {student?.FirstName}


{student?.LastName}, Branch: {student?.Branch}, Gender:{student?.Gender}");

//Let us Delete the Student Whose Id = 1 i.e. student object

if (student != null)

DeleteStudent(student.StudentId);

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
987

//The following Method is going to add a New Student into the database

//And return the new StudentId

public static int AddStudent(Student student)

int result = 0;

try

using var context = new EFCoreDbContext();

var FirstNameParam = new SqlParameter("FirstName", student.FirstName);

var LastNameParam = new SqlParameter("LastName", student.LastName);

var BranchParam = new SqlParameter("Branch", student.Branch);

var GenderParam = new SqlParameter("Gender", student.Gender);

var StudentIdOutParam = new SqlParameter

ParameterName = "StudentId",

SqlDbType = SqlDbType.Int,

Direction = ParameterDirection.Output

};

int NumberOfRowsAffected = context.Database.ExecuteSqlInterpolated($"EXEC


spInsertStudent @FirstName={FirstNameParam}, @LastName={LastNameParam},
@Branch={BranchParam}, @Gender={GenderParam}, @StudentId={StudentIdOutParam}
OUTPUT");

if (NumberOfRowsAffected > 0)

{
988

// Retrieve the value of the OUT parameter

result = (int)StudentIdOutParam.Value;

catch (Exception ex)

Console.WriteLine($"AddStudent: Error Occurred: {ex.Message}");

return result;

//The following Method is going to return a Single Student from the database based on
Student Id

//And return the new StudentId

public static Student? GetStudentById(int StudentId)

Student? student = null;

try

using var context = new EFCoreDbContext();

var result = context.Students

.FromSqlInterpolated($"EXEC spGetStudentByStudentId @StudentId={StudentId}")

.ToList();

if (result.Count > 0)

{
989

student = result.FirstOrDefault();

catch (Exception ex)

Console.WriteLine($"GetStudentById: Error Occurred: {ex.Message}");

return student;

//The following Method is going to return all Students from the database

public static List<Student>? GetAllStudents()

List<Student>? students = new List<Student>();

try

using var context = new EFCoreDbContext();

//Returning All Students

students = context.Students

.FromSqlInterpolated($"EXEC spGetAllStudents")

.ToList();

catch (Exception ex)

Console.WriteLine($"GetAllStudents: Error Occurred: {ex.Message}");


990

return students;

//The following Method is going to Update an Existing Student into the database

public static void UpdateStudent(Student student)

try

using var context = new EFCoreDbContext();

var StudentIdParam = new SqlParameter("StudentId", student.StudentId);

var FirstNameParam = new SqlParameter("FirstName", student.FirstName);

var LastNameParam = new SqlParameter("LastName", student.LastName);

var BranchParam = new SqlParameter("Branch", student.Branch);

var GenderParam = new SqlParameter("Gender", student.Gender);

//result: Returns the number of rows affected

var result = context.Database.ExecuteSqlInterpolated($"EXEC spUpdateStudent


@StudentId={StudentIdParam}, @FirstName={FirstNameParam},
@LastName={LastNameParam}, @Branch={BranchParam}, @Gender={GenderParam}");

if (result > 0)

Console.WriteLine($"\nEntity with StudentId: {student.StudentId} Updated in the


Database");

else

{
991

Console.WriteLine($"\nEntity with StudentId: {student.StudentId} Not Updated in the


Database");

catch (Exception ex)

Console.WriteLine($"AddStudent: Error Occurred: {ex.Message}");

//The following Method is going to Delete an Existing Student from the database based on
Student Id

//And return the new StudentId

public static Student? DeleteStudent(int StudentId)

Student? student = null;

try

using var context = new EFCoreDbContext();

var StudentIdParam = new SqlParameter("StudentId", StudentId);

//Deleting an Existing Student Entity

//result: Returns the number of rows affected

var result = context.Database.ExecuteSqlInterpolated($"EXEC spDeleteStudent


@StudentId={StudentIdParam}");

if (result > 0)

{
992

Console.WriteLine($"\nEntity with StudentId: {StudentId} Deleted from the Database");

else

Console.WriteLine($"\nEntity with StudentId: {StudentId} Not Deleted from the Database");

catch (Exception ex)

Console.WriteLine($"DeleteStudent: Error Occurred: {ex.Message}");

return student;

}
Considerations and Best Practices of Executing Stored Procedure Using
EF Core
 Performance: Stored procedures can sometimes offer performance
advantages, especially for complex operations, as they are executed on the
server side.
 Maintainability: While stored procedures encapsulate logic within the
database, which can be beneficial, they also create an additional layer of
code to maintain outside your application.
 Portability: Relying on stored procedures can reduce the portability of your
application across different database systems, as stored procedures are often
specific to a particular SQL dialect.
 Debugging: Debugging issues can be more challenging with stored
procedures since the logic is executed outside the EF Core and .NET
environments.
 Compatibility: Ensure that your version of Entity Framework Core supports
the features you need for working with stored procedures, as EF Core’s
support for stored procedures has evolved over time.
993

Differences Between ExecuteSqlRaw and FromSqlRaw in Entity


Framework Core
In Entity Framework, ExecuteSqlRaw and FromSqlRaw are two methods that allow us to
execute raw SQL queries or Stored Procedures against the database. They have some key
differences in terms of their usage and the results they return:
Purpose:
 FromSqlRaw: This method maps the results of a RAW SQL Query or Stored
Procedure to an entity type. It allows you to retrieve data from the database
and map it to entity objects. So, if you want to retrieve table data (entities or
complex types) from the database, you need to use the FromSqlRaw Method.
 ExecuteSqlRaw: This method executes RAW SQL Queries or Stored
Procedures that do not return entities or complex types. It is typically used for
executing SQL commands that modify the database (e.g., INSERT, UPDATE,
DELETE) or return scalar values (e.g., COUNT, SUM).
Return Type:
 FromSqlRaw: It returns an IQueryable<TEntity> or DbSet<TEntity>, where
TEntity is an entity type we need to specify. This allows us to work with the
results as regular entity objects. If you are returning a single entity, you need
to use DbSet<TEntity>, and if you are returning multiple entities, then you
need to use IQueryable<TEntity>.
 ExecuteSqlRaw: It does not return data as entities. Instead, it typically
returns the number of rows affected by the query or, in some cases, the result
of a scalar query.
Usage:
 FromSqlRaw: Typically used for querying data from the database using Raw
SQL or Stored Procedure and then mapping the results to entity objects. For
example, you can use it to retrieve a list of entities from the database based
on some condition or without any condition.
 ExecuteSqlRaw: It is typically used for executing RAW SQL Commands or
Stored Procedures that don’t return entity objects. It is mainly used to Perform
INSERT, UPDATE, and DELETE operations or for executing scalar queries to
retrieve a single value.
Safety:
 FromSqlRaw: Entity Framework Core will attempt to map the query results to
the specified entity type, so we should use it when we expect the query to
return data that can be mapped to the entity. Incorrect mappings can lead to
runtime errors.
 ExecuteSqlRaw: It is safer for executing non-query SQL commands because
it doesn’t involve mapping to entities. However, you must still be cautious with
user inputs and ensure they are correctly parameterized to prevent SQL
injection.
So, FromSqlRaw is used for querying and mapping data to entity objects, while
ExecuteSqlRaw is used for executing raw SQL commands and scalar queries that do not
return entities. Both methods have their own specific use cases and should be chosen
based on the task you want to perform.
Entity Framework Core Inheritance (TPH, TPT, and TPC)
In this article, I will discuss Entity Framework Core (EF Core) Inheritance (TPH, TPT,
and TPC) with Examples. Please read our previous article discussing Stored Procedures
994

in Entity Framework Core with Examples. At the end of this article, you will understand the
following pointers:
1. What do you mean by Entity Framework Core Inheritance?
2. Table Per Hierarchy (TPH) Inheritance in Entity Framework Core
3. Example to Understand Table Per Hierarchy (TPH) in EF Core
4. Table Per Type (TPT) Inheritance in Entity Framework core
5. Example to Understand Table Per Type (TPT) in EF Core
6. Table Per Concrete Type (TPC) Inheritance in Entity Framework Core
7. Example to Understand Table Per Concrete Type (TPC) Inheritance in EF
Core
8. TPH vs. TPT vs. TPC in EF Core
9. How to Choose the Right Strategy?
What do you mean by Entity Framework Core Inheritance?
Inheritance is one of the Object-Oriented Programming (OOP) Principles where a class
(known as a Derived, Child, or Subclass) can inherit properties and methods from another
class (known as a Base, Parent, or Child class). This allows code reusability functionality.
Entity Framework Core supports inheritance, allowing you to map an inheritance hierarchy
of .NET classes to a relational database. EF Core supports three common inheritance
mapping patterns:
1. Table Per Hierarchy (TPH) – 1 Table Per Hierarchy
2. Table Per Type (TPT) – 1 Table Per Type
3. Table Per Concrete Class (TPC) – 1 Table Per Each Concrete Class, not
for Abstract classes or Interfaces
Table Per Hierarchy (TPH) Inheritance in Entity Framework Core:
Table Per Hierarchy (TPH) is called Single-Table Inheritance. In this strategy, all the
classes in the inheritance hierarchy are stored in a single database table. There’s a
discriminator column that EF Core uses to differentiate between the types.

NextStayEnchanted by the Beautiful City near Cambodia Border - Nếm


TV00:23 / 02:4810 Sec
 This is the default inheritance mapping strategy in EF Core.
 A single table stores the data for all types in the hierarchy.
 The table includes columns for all properties of all classes in the hierarchy.
 A discriminator column is used to identify the type of each row.
Example to Understand Table Per Hierarchy (TPH) in EF Core
Table Per Hierarchy (TPH) is one of the inheritance strategies available in Entity Framework
Core (EF Core). This can be a more efficient and straightforward approach when compared
to other inheritance strategies like Table-Per-Type (TPT) or Table-Per-Concrete-Type
(TPC). Here’s how we can implement Table-Per-Hierarchy inheritance in EF Core:
Base Class:
We must start by defining a base class representing the common properties and fields for
all the entities in our inheritance hierarchy. So, create the following Base Entity.
namespace EFCoreCodeFirstDemo.Entities

{
995

public class BaseEntity

public int Id { get; set; }

public string CommonProperty { get; set; }

}
Derived Classes:
Next, we must create derived classes that inherit from the base class and represent the
specialized entities in your hierarchy. Each derived class can have additional properties
specific to that entity type. So, create the following Derived Entities:
namespace EFCoreCodeFirstDemo.Entities

public class DerivedEntityA : BaseEntity

public string PropertyA { get; set; }

public class DerivedEntityB : BaseEntity

public string PropertyB { get; set; }

}
TPH Configuration in EF Core:
In EF Core, you must configure TPH inheritance using Fluent API in your DbContext class.
Here, we need to specify that all entities in the hierarchy should be stored in a single
database table and use a discriminator column to differentiate between different entity
types. Modify the DbContext class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities
996

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<BaseEntity>()

.ToTable("Entities")

.HasDiscriminator<string>("entity_type")

.HasValue<DerivedEntityA>("EntityA")

.HasValue<DerivedEntityB>("EntityB");

public DbSet<BaseEntity> BaseEntites { get; set; }

}
Points to Remember:
 Discriminator Column: EF Core will add a discriminator column to the
Entities database table named entity_type, which we configured using the
HasDiscriminator method. This column will store a value that indicates the
type of entity each row represents. The discriminator column, i.e., entity_type
column, will contain EntityA or EntityB, which we configured using the
HasValue Method.
997

 Querying: When we query the database, EF Core automatically uses the


discriminator column to determine the entity type and return the appropriate
derived class instances.
 Inserting and Updating: When we insert or update entities, EF Core will set
the discriminator value appropriately based on the type of entity we are
working with.
With the above changes, open the Package Manager Console and Execute the add-
migration and update-database commands as follows. You can give any name to your
migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be
given earlier.

Now, if you verify the database, then you will see the following:

Modify the Program class to check whether TPH Inheritance works as expected. The
following example code is self-explained, so please go through the comment lines for a
better understanding:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

{
998

public class Program

static async Task Main(string[] args)

try

//Use Inheritance in our Code

//Now, you can create and work with instances of the derived classes

//and save them to the database

using (var context = new EFCoreDbContext())

var derivedEntityA = new DerivedEntityA { PropertyA = "SomeValueA", CommonProperty


= "SomeCommonValue" };

var derivedEntityB = new DerivedEntityB { PropertyB = "SomeValueB", CommonProperty


= "SomeCommonValue" };

context.BaseEntites.AddRange(derivedEntityA, derivedEntityB);

context.SaveChanges();

Console.WriteLine("Entities are Added");

//Query the Inheritance Hierarchy

//You can query the inheritance hierarchy using LINQ queries

using (var context = new EFCoreDbContext())

var baseEntities = context.BaseEntites.ToList();


999

foreach (var vehicle in baseEntities)

if (vehicle is DerivedEntityA derivedEntityA)

Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntityA.Id}, PropertyA:


{derivedEntityA.PropertyA}, CommonProperty: {derivedEntityA.CommonProperty}");

else if (vehicle is DerivedEntityB derivedEntityB)

Console.WriteLine($"\tDerivedEntityB: Id: {derivedEntityB.Id}, PropertyA:


{derivedEntityB.PropertyB}, CommonProperty: {derivedEntityB.CommonProperty}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:
1000

If you verify the database table, you will see the following data.

Performance Considerations: The table can become wide with many nullable columns,
especially if the inheritance hierarchy is large with many properties. Consider the
performance implications when using TPH, especially when dealing with many derived
types with many unique properties.
So, the above example demonstrates how to implement Table-Per-Hierarchy (TPH)
inheritance in EF Core to store related entities in a single database table while preserving
the type information.
Table Per Type (TPT) Inheritance in Entity Framework core:
Table Per Type (TPT) is also known as Class Table Inheritance. In this strategy, each
class in the inheritance hierarchy has its own corresponding database table (database
tables for both base/parent and derived/child classes). Common properties are stored in a
shared table, while specific properties for each derived class are stored in their respective
tables. EF Core automatically creates relationships between the shared table and derived
tables.
 Each class in the hierarchy is mapped to a separate table.
 Properties that belong to the base class are stored in the base table, and
properties specific to derived classes are stored in their own tables.
 Relationships between tables reflect the inheritance structure and are usually
implemented using primary and foreign keys.
 The Base Table Primary Key will be created as the Primary Key and Foreign
Key in the Child Tables.
Example to Understand Table Per Type (TPT) in EF Core:
In TPT, each class in the inheritance hierarchy is mapped to its own table in the database.
This approach can be useful when you clearly distinguish between base and derived
entities and don’t want to mix derived entity properties in the base table. Here’s how you
can implement TPT inheritance in EF Core:
Define Your Base Class:
Start by defining a base class representing the common properties and fields shared by all
the classes in your inheritance hierarchy. This base class should be decorated with
the [Table] attribute to specify the table name explicitly and with [Key] to indicate the
primary key. This is the parent class in our model. It will be represented by its own table in
the database. So, create the following Base Entity.
using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations.Schema;
1001

namespace EFCoreCodeFirstDemo.Entities

[Table("BaseTable")]

public class BaseEntity

[Key]

public int Id { get; set; }

public string CommonProperty { get; set; }

}
Define Your Derived Classes:
Each derived class will also be represented by its own table in the database. The table for a
derived class includes a primary key column (this will be the Primary Key Column of the
Base table) that is also a foreign key referencing the base class table. Create the derived
classes that inherit from the base class. Each derived class should have its own properties
and fields, and they can be decorated with [Table] attributes to specify their table names.
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("DerivedTable1")]

public class DerivedEntity1 : BaseEntity

public string Property1 { get; set; }

[Table("DerivedTable2")]

public class DerivedEntity2 : BaseEntity

{
1002

public string Property2 { get; set; }

}
Configure the Model:
In your DbContext class, use the modelBuilder to configure the TPT inheritance strategy
using the HasBaseType method.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<DerivedEntity1>().HasBaseType<BaseEntity>();

modelBuilder.Entity<DerivedEntity2>().HasBaseType<BaseEntity>();

public DbSet<BaseEntity> BaseEntites { get; set; }

}
1003

Migrate and Apply Database Changes:


After defining your entities and configuring the model, you can use EF Core’s migration
commands to create or update the database schema based on your model. So, open the
Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.

Now, if you verify the database, then you will see the following:

Modify the Program class to check whether TPT Inheritance is working as expected. The
following example code is self-explained, so please go through the comment lines for a
better understanding:
using EFCoreCodeFirstDemo.Entities;
1004

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Use Inheritance in our Code

//Now, you can create and work with instances of the derived classes

//and save them to the database

using (var context = new EFCoreDbContext())

var derivedEntityA = new DerivedEntity1 { Property1 = "SomeValue1", CommonProperty =


"SomeCommonValue" };

var derivedEntityB = new DerivedEntity2 { Property2 = "SomeValue2", CommonProperty =


"SomeCommonValue" };

context.BaseEntites.AddRange(derivedEntityA, derivedEntityB);

context.SaveChanges();

Console.WriteLine("Entities are Added");

//Query the Inheritance Hierarchy

//You can query the inheritance hierarchy using LINQ queries

using (var context = new EFCoreDbContext())


1005

var baseEntities = context.BaseEntites.ToList();

foreach (var vehicle in baseEntities)

if (vehicle is DerivedEntity1 derivedEntityA)

Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntityA.Id}, Property1:


{derivedEntityA.Property1}, CommonProperty: {derivedEntityA.CommonProperty}");

else if (vehicle is DerivedEntity2 derivedEntityB)

Console.WriteLine($"\tDerivedEntityB: Id: {derivedEntityB.Id}, Property2:


{derivedEntityB.Property2}, CommonProperty: {derivedEntityB.CommonProperty}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
1006

}
Output:

If you verify the database table, you will see the following data.

Considerations for TPT


 Performance: TPT internally uses joins to construct the entity. So, querying
for derived types can be slower than Table-Per-Hierarchy (TPH), which uses
a single table to store the entire inheritance hierarchy.
 Schema Complexity: TPT can result in a more complex database schema if
you have many derived types, as each requires its own table.
 Data Integrity: Using foreign keys in TPT helps maintain referential integrity
across the different tables in the inheritance hierarchy.
Table Per Concrete Type (TPC) Inheritance in Entity Framework Core:
Table Per Concrete Type/Class (TPC) is a strategy where each concrete class in the
inheritance hierarchy has its own database table, and there is no shared table for common
properties. EF Core doesn’t automatically create relationships between tables in TPC.
 Each non-abstract class is mapped to its own table.
 All properties, including those inherited, are mapped to columns in the
database table.
 This approach can lead to redundancy if the hierarchy is deep and classes
share many properties.
Example to Understand Table Per Concrete Type (TPC) Inheritance in EF
Core
TPC is a pattern where each concrete class in the inheritance hierarchy is mapped to its
own table. Each table contains the columns for the class properties, including inherited
properties. Unlike Table-Per-Hierarchy (TPH), where a single table includes data for all
1007

types in the hierarchy, or Table-Per-Type (TPT), where each type has its own table but only
stores data for the properties declared at that level, TPC has separate tables that are not
connected through foreign keys and each table repeats the columns for the inherited
properties. This strategy is useful when avoiding joins and efficiently retrieving data for a
specific derived type. Here’s how you can implement TPC inheritance in EF Core:
Define Your Base Class:
Start by defining a base class (abstract class or interface) representing the common
properties and fields shared by all the child classes in your inheritance hierarchy. In our
example, we are going to use the following abstract class.
using System.ComponentModel.DataAnnotations;

namespace EFCoreCodeFirstDemo.Entities

public abstract class BaseEntity

[Key]

public int Id { get; set; }

public string CommonProperty { get; set; }

}
Define Your Derived Classes:
Create the derived classes that inherit from the abstract base class. Each derived class
should have its own properties and fields, and they can be decorated with [Table] attributes
to specify their table names. But in our example, we will use Fluent API configuration to
provide the table names. So, create the following derived concrete classes.
namespace EFCoreCodeFirstDemo.Entities

public class DerivedEntity1 : BaseEntity

public string Property1 { get; set; }

public class DerivedEntity2 : BaseEntity


1008

public string Property2 { get; set; }

}
Configure the Model:
In your DbContext class, use the modelBuilder to configure the TPC inheritance strategy. All
the concrete types are mapped to individual tables in the TPC mapping pattern. For TPC
mapping call the modelBuilder.Entity<BaseEntity>().UseTpcMappingStrategy() on each
root entity type, as shown below.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<BaseEntity>().UseTpcMappingStrategy();

modelBuilder.Entity<DerivedEntity1>().ToTable("DerivedTable1");

modelBuilder.Entity<DerivedEntity2>().ToTable("DerivedTable2");

}
1009

public DbSet<BaseEntity> BaseEntites { get; set; }

}
Migrate and Apply Database Changes:
After defining your entities and configuring the model, you can use EF Core’s migration
commands to create or update the database schema based on your model. So, open the
Package Manager Console and Execute the add-migration and update-database
commands as follows. You can give any name to your migration. Here, I am giving Mig3.
The name that you are giving it should not be given earlier.

Now, you will have separate database tables for each derived type (DerivedTable1 and
DerivedTable2). These tables will include the properties inherited from the base class
(BaseEntity) and the properties specific to each derived class. Now, if you verify the
database, then you will see the following:
1010

Modify the Program class to check whether TPC Inheritance is working as expected. The
following example code is self-explained, so please go through the comment lines for a
better understanding. In the example below, the OfType method filters the entities retrieved
from the DbSet based on their concrete type, either DerivedEntity1 or DerivedEntity2. The
filtered entities are then loaded into memory using the ToList method and displayed in the
console.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

{
1011

//Use Inheritance in our Code

//Now, you can create and work with instances of the derived classes

//and save them to the database

using (var context = new EFCoreDbContext())

var derivedEntityA = new DerivedEntity1 { Property1 = "SomeValue1", CommonProperty =


"SomeCommonValue" };

context.BaseEntites.Add(derivedEntityA);

var derivedEntityB = new DerivedEntity2 { Property2 = "SomeValue2", CommonProperty =


"SomeCommonValue" };

context.BaseEntites.Add(derivedEntityB);

context.SaveChanges();

Console.WriteLine("Entities are Added");

//Query the Inheritance Hierarchy

//You can query the inheritance hierarchy using LINQ queries

using (var context = new EFCoreDbContext())

var DerivedEntities1 = context.BaseEntites.OfType<DerivedEntity1>().ToList();

foreach (DerivedEntity1 derivedEntity1 in DerivedEntities1)

Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntity1.Id}, Property1:


{derivedEntity1.Property1}, CommonProperty: {derivedEntity1.CommonProperty}");

var DerivedEntities2 = context.BaseEntites.OfType<DerivedEntity2>().ToList();


1012

foreach (DerivedEntity2 derivedEntity2 in DerivedEntities2)

Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntity2.Id}, Property2:


{derivedEntity2.Property2}, CommonProperty: {derivedEntity2.CommonProperty}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Now, if you verify the database table, you will see the following data:
1013

TPH vs. TPT vs. TPC in EF Core


In Entity Framework Core (EF Core), there are three common inheritance mapping
strategies for modeling object-oriented inheritance hierarchies in a relational database:
Table-Per-Hierarchy (TPH), Table-Per-Type (TPT), and Table-Per-Concrete-Type (TPC).
Each strategy has its own advantages and disadvantages, and the choice of which one to
use depends on your specific application requirements and design considerations. Here’s
an overview of these three inheritance mapping strategies:
Table-Per-Hierarchy (TPH) in EF Core:
In TPH, the entire class hierarchy is mapped to a single database table. The table includes
columns for all properties of all classes in the hierarchy. A discriminator column is used to
identify which class each row corresponds to.
Advantages of Table-Per-Hierarchy (TPH in EF Core):
 Simple database schema. It results in fewer tables and less redundancy in
the database schema since all types share the same table.
 Simple to implement and understand.
 Generally, it offers the best query performance since it does not require
joining tables.
Disadvantages of Table-Per-Hierarchy (TPH in EF Core):
 The table can have many nullable columns if many properties are specific to
only some derived classes.
 It is not suitable when the derived types have a significantly different set of
properties.
Table-Per-Type (TPT) in EF Core:
Each class in the hierarchy in TPT is mapped to its own table. Properties defined in a base
class are stored in a base table, and each derived class has its own table containing only
the properties defined for that derived class. The tables are related through foreign keys.
Advantages of Table-Per-Type (TPT in EF Core):
 Clear separation of data: Each type has its own table, making the schema
intuitive.
 There are no nullable columns since each table contains only the columns
relevant to the respective type.
 Normalized database schema.
 Easy to maintain data integrity.
Disadvantages of Table-Per-Type (TPT in EF Core):
1014

 Increased schema complexity due to multiple tables.


 Requires more complex joins, which can lead to worse performance for
retrieval.
 Potentially more complex insert and update operations.
Table-Per-Concrete-Type (TPC) in EF Core:
In TPC, each concrete class in the hierarchy is mapped to its own table and includes
columns for all class properties, including inherited ones. No tables are created for abstract
classes or interfaces. There are no relations between the tables for each type.
Advantages of Table-Per-Concrete-Type (TPC in EF Core):
 Efficient for querying specific derived types without joins.
 It is suitable when derived types have significantly different sets of properties.
 There are no nullable columns since each table contains only the columns for
the specific type.
Disadvantages of Table-Per-Concrete-Type (TPC in EF Core):
 Increased schema complexity due to multiple tables.
 This may lead to redundant data if the base class properties are stored in
each table, increasing storage requirements.
 It is more complex to maintain data integrity across tables.
Choosing the Right Strategy
When choosing between these strategies, you should consider:
 Performance: TPH is generally the fastest, while TPT can suffer from
performance issues due to the need for joins.
 Data Integrity: TPT and TPC can provide better data integrity and make it
easier to enforce constraints.
 Complexity: TPH typically results in the simplest database schema and is
easiest to work with in EF Core, while TPT and TPC can increase the
complexity of the model.
Choosing the right inheritance mapping strategy depends on factors such as your data
model, querying requirements, and your willingness to make performance considerations.
TPH is often a good choice for hierarchies with shared properties and when you want a
more compact schema. TPT and TPC in EF Core are better suited when you want a clear
separation of data and better performance for querying specific derived types. Ultimately,
the choice should align with your application needs and database design goals.

Transactions in Entity Framework Core (EF Core)


In this article, I will discuss Transactions in Entity Framework Core (EF Core) with
Examples. Please read our previous article discussing Entity Framework Core
Inheritance (TPH, TPT, and TPC) with Examples. At the end of this article, you will
understand the following pointers:
1. What are Transactions in Entity Framework Core?
2. Different Ways to Implement Transactions in Entity Framework Core
3. Example to Understand Transactions in Entity Framework Core
4. Automatic Transactions with SaveChanges in EF Core
5. Manual Transactions in Entity Framework Core using
Database.BeginTransaction()
1015

6. Asynchronous Transactions in Entity Framework Core


7. Distributed Transaction using TransactionScope in EF Core
8. Using Existing Transactions in EF Core
9. Choosing The Right Transaction in Entity Framework Core
10. Transaction Challenges in Entity Framework Core
What are Transactions in Entity Framework Core?
Transactions in Entity Framework Core (EF Core) are crucial for maintaining data integrity
and consistency, especially when performing multiple operations that need to be treated as
a single unit of work. EF Core provides several ways to handle transactions, giving us more
control over executing and committing database operations.
A Transaction is a set of operations (multiple DML Operations) that ensures that all
database operations succeed or fail to ensure data consistency. This means the job is
never half done; either all of it is done, or nothing is done.
Different Ways to Implement Transactions in Entity Framework Core:
In Entity Framework Core (EF Core), there are several ways to implement transactions to
ensure that a series of operations on a database are executed as a single unit of work.
They are as follows:

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:22 / 03:1710 Sec
 Automatic Transaction: The SaveChanges() method automatically wraps
the changes in a transaction. If any command fails, it throws an exception,
and all changes are rolled back.
 Manual Transaction: You can manually begin, commit, or rollback
transactions using the Database property of the
DbContext.Database.BeginTransaction().
 Distributed Transaction: The TransactionScope automatically manages
transaction lifetimes, allowing operations across multiple contexts or
databases to participate in a single transaction.
 Using Existing Transaction: You can attach an existing transaction to the
DbContext using DbContext.Database.UseTransaction():
 Asynchronous Transactions: You can also manage transactions
asynchronously, which is particularly useful in web applications where you
don’t want to block threads.
Example to Understand Transactions in Entity Framework Core:
Let us see examples to understand transactions in Entity Framework Core. So, first, create
the following model class:
namespace EFCoreCodeFirstDemo.Entities

public class Student

{
1016

public int StudentId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

}
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Student> Students { get; set; }

}
Migrate and Apply Database Changes:
1017

After defining your entities and configuring the model, you can use EF Core’s migration
commands to create or update the database schema based on your model. So, open the
Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.

Automatic Transactions with SaveChanges in EF Core:


By default, when we call the SaveChanges() method, EF Core will automatically wrap our
changes in a transaction. This transaction is committed or rolled back automatically if an
exception occurs. That means every time we call the SaveChanges() method, EF Core
wraps that operation in a transaction. This transaction includes all the changes made to the
database context since the last time SaveChanges()was called. The syntax is given below:

The SaveChanges methods automatically begin and commit a transaction if one is not
already in progress. For a better understanding, please modify the Program class as
follows:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

{
1018

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

Student std1 = new Student()

FirstName = "Pranaya",

LastName = "Rout"

};

context.Students.Add(std1);

// This will automatically use a transaction.

context.SaveChanges();

Student std2 = new Student()

FirstName = "Tarun",

LastName = "Kumar"

};

context.Students.Add(std2);

// This will automatically use a new transaction.

context.SaveChanges();

Console.WriteLine("Entities are Saved");

Console.Read();

}
1019

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
In this example, the SaveChanges method is used to persist the changes to the database,
and the transaction management ensures that all operations are committed or none if an
error occurs. It is important to handle exceptions properly to avoid unintended writes to the
database and ensure that transactions are always properly completed or rolled back.
Manual Transactions in Entity Framework Core using
Database.BeginTransaction()
If you need more control over the transaction, for instance, if you want to include several
SaveChanges() calls in one transaction, you can start a transaction manually
using Database.BeginTransaction() or Database.BeginTransactionAsync(). That means
you can begin a transaction manually using the BeginTransaction method on the database
connection or the Database property of your context object. If the transaction is completed
successfully, call the Commit method. If any exception occurs, invoke the Rollback method
using the transaction object. The following is the syntax:
1020

In Entity Framework Core (EF Core), you can manually control transactions using the
Database.BeginTransaction() or Database.BeginTransactionAsync() methods on your
DbContext. This is useful when you want to execute multiple operations as a single unit of
work, ensuring that all operations succeed or fail. You can roll back all the changes if any
part of the transaction fails. Please look at the following example for a better understanding:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using (var context = new EFCoreDbContext())

// Begin a new transaction

using (var transaction = context.Database.BeginTransaction())

try

// Perform database operations within the transaction

Student std1 = new Student()

FirstName = "Pranaya",

LastName = "Rout"
1021

};

context.Students.Add(std1);

// SaveChanges() but do not commit yet, this will persist changes but hold the commit until

// we are sure that all operations succeed

context.SaveChanges();

Student std2 = new Student()

FirstName = "Tarun",

LastName = "Kumar"

};

context.Students.Add(std2);

// SaveChanges() but do not commit yet, this will persist changes but hold the commit until

// we are sure that all operations succeed

context.SaveChanges();

// If everything is fine until here, commit the transaction

transaction.Commit();

Console.WriteLine("Entities are Saved");

catch (Exception ex)

// If an exception is thrown, roll back the transaction

transaction.Rollback();

// Handle or throw the exception as needed

Console.WriteLine(ex.Message);
1022

throw;

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
In the above code:
 BeginTransaction() is called on the Database property of the context to start a
new transaction.
 Database operations are then performed normally, such as adding an entity
and saving changes.
 Commit() is called on the transaction object to commit all the operations as an
atomic unit if all operations succeed without throwing exceptions.
 If there is an exception, Rollback() is called to undo all the operations that
were part of this transaction.
 It is important to wrap the transaction code within a try-catch block to ensure
exceptions are handled, and resources are cleaned up properly.
Asynchronous Transactions in Entity Framework Core
In EF Core 2.1 and later, you can begin a transaction explicitly using the
BeginTransactionAsync method for asynchronous operations. That means you can also
manage transactions asynchronously, which is particularly useful in web applications where
you don’t want to block threads. The syntax is given below:
1023

It’s also a good practice to use async and await for database operations to avoid blocking
calls, especially in a web application environment. For a better understanding, please have
a look at the following example.
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using (var context = new EFCoreDbContext())

// Begin a transaction asynchronously

await using (var transaction = await context.Database.BeginTransactionAsync())

{
1024

try

// Perform database operations within the transaction

Student std1 = new Student()

FirstName = "Pranaya",

LastName = "Rout"

};

context.Students.Add(std1);

// SaveChangesAsync() but do not commit yet, this will persist changes but hold the commit
until

// we are sure that all operations succeed

await context.SaveChangesAsync();

Student std2 = new Student()

FirstName = "Tarun",

LastName = "Kumar"

};

context.Students.Add(std2);

// SaveChangesAsync() but do not commit yet, this will persist changes but hold the commit
until

// we are sure that all operations succeed

await context.SaveChangesAsync();

// If everything is fine until here, commit the transaction


1025

await transaction.CommitAsync();

Console.WriteLine("Entities are Saved");

catch (Exception ex)

// If there is any error, roll back all changes

await transaction.RollbackAsync();

// Handle or throw the exception as needed

Console.WriteLine(ex.Message);

throw;

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Distributed Transaction using TransactionScope in EF Core:
In Entity Framework Core, the TransactionScope class can manage transaction boundaries
across multiple database contexts or different types of databases. It’s a .NET Framework
1026

feature that allows you to run a code block within a transaction without interacting directly
with the transaction itself. The syntax to use Transaction Scope in EF Core is given below:

When working with TransactionScope, it is important to note that it relies on the open
database connection within the scope. Transactions are automatically promoted to a
distributed transaction if necessary (for example, when the operations span multiple
databases). For a better understanding, please have a look at the following example:
using EFCoreCodeFirstDemo.Entities;

using System.Transactions;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

{
1027

// Define the scope of the transaction

var options = new TransactionOptions

IsolationLevel = IsolationLevel.ReadCommitted,

Timeout = TransactionManager.DefaultTimeout // Default is 1 minute

};

// Start a new TransactionScope

using (var scope = new TransactionScope(TransactionScopeOption.Required, options))

try

using (var context1 = new EFCoreDbContext())

// Perform data access using context1 here

Student std1 = new Student()

FirstName = "Pranaya",

LastName = "Rout"

};

context1.Students.Add(std1);

context1.SaveChanges();

} // The actual INSERT command is sent to the database here

// You can even use another context or database operation here

using (var context2 = new EFCoreDbContext())


1028

// Perform data access using context2 here

Student std2 = new Student()

FirstName = "Rakesh",

LastName = "Kumar"

};

context2.Students.Add(std2);

context2.SaveChanges();

} // The actual INSERT command is sent to the database here

// Complete the scope here, if everything succeeded

// If all operations complete successfully, commit the transaction

scope.Complete();

Console.WriteLine("Entities are Saved");

catch (Exception ex)

// Handle errors and the transaction will be rolled back

Console.WriteLine(ex.Message);

// The TransactionScope is disposed without calling Complete(), so the transaction will be


rolled back

// Handle the exception as needed

Console.WriteLine(ex.Message);

}
1029

} // The TransactionScope is disposed here, committing or rolling back the transaction

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
In this Example:
 The TransactionScope is created with a specified isolation level and timeout
period.
 The TransactionScope is created using a “using” block, ensuring that it is
disposed of properly, committing or rolling back the transaction depending on
whether the scope.Complete() is called.
 Within the transaction scope, multiple database operations are performed
across different contexts.
 Database operations are performed within different contexts but are covered
by the same transaction scope.
 If everything executes successfully, call scope.Complete() commits all the
operations.
 scope.Complete() indicates that all operations within the scope are completed
successfully. If this method is not called, the transaction will be rolled back
when the scope is disposed.
 If an exception is thrown, or if scope.Complete() is not called, the transaction
will be rolled back, and none of the operations will be persisted.
Using Existing Transaction in EF Core:
You can attach an existing transaction to the DbContext using
DbContext.Database.UseTransaction(). The syntax is given below:
1030

In Entity Framework Core (EF Core), if you want to use an existing database transaction
across multiple operations or contexts, you can pass the existing DbTransaction to another
DbContext instance. This allows you to share the same transaction across multiple
contexts, ensuring that all operations commit or rollback together. This can be helpful in
scenarios where a transaction is started using the underlying database connection, and you
want EF Core to enlist in that transaction.
First, let’s assume you have a method that starts a transaction using the database
connection:
public static DbTransaction BeginDatabaseTransaction(EFCoreDbContext context)

var connection = context.Database.GetDbConnection();

if (connection.State != ConnectionState.Open)

connection.Open();

return connection.BeginTransaction();

}
Now, you can use this DbTransaction in your operations with EF Core:
public static void UseExistingTransaction(EFCoreDbContext context, DbTransaction
transaction)

// Enlist in an existing transaction


1031

context.Database.UseTransaction(transaction);

try

// Perform operations within the transaction

Student std1 = new Student()

FirstName = "Rakesh",

LastName = "Kumar"

};

context.Students.Add(std1);

context.SaveChanges();

// You can perform more operations here, and they will all be part of the same transaction

// ...

// Commit or rollback is controlled outside this method since the transaction was passed in

catch (Exception ex)

// Handle exception

// ...

Console.WriteLine($"Error: {ex.Message}");

// Note that the responsibility for rolling back the transaction, if needed, lies outside this
method

finally
1032

// Detach the context from the transaction after the work is done

context.Database.UseTransaction(null);

}
And here is how you can call UseExistingTransaction:
using (var context = new EFCoreDbContext())

using (var transaction = BeginDatabaseTransaction(context))

try

// Perform data access using context2 here

Student std1 = new Student()

FirstName = "Pranaya",

LastName = "Rout"

};

context.Students.Add(std1);

UseExistingTransaction(context, transaction);

// If everything was successful, commit the transaction

transaction.Commit();

Console.WriteLine("Entities Added");

}
1033

catch (Exception ex)

// If there was an error, roll back the transaction

transaction.Rollback();

throw;

}
The complete example code is given below:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

using System.Data.Common;

using System.Data;

namespace EFCoreCodeFirstDemo

public class Program

public static DbTransaction BeginDatabaseTransaction(EFCoreDbContext context)

var connection = context.Database.GetDbConnection();

if (connection.State != ConnectionState.Open)

connection.Open();

}
1034

return connection.BeginTransaction();

public static void UseExistingTransaction(EFCoreDbContext context, DbTransaction


transaction)

// Enlist in an existing transaction

context.Database.UseTransaction(transaction);

try

// Perform operations within the transaction

Student std1 = new Student()

FirstName = "Rakesh",

LastName = "Kumar"

};

context.Students.Add(std1);

context.SaveChanges();

// You can perform more operations here, and they will all be part of the same transaction

// ...

// Commit or rollback is controlled outside this method since the transaction was passed in

catch (Exception ex)

// Handle exception
1035

// ...

Console.WriteLine($"Error: {ex.Message}");

// Note that the responsibility for rolling back the transaction, if needed, lies outside this
method

finally

// Detach the context from the transaction after the work is done

context.Database.UseTransaction(null);

static async Task Main(string[] args)

try

using (var context = new EFCoreDbContext())

using (var transaction = BeginDatabaseTransaction(context))

try

// Perform data access using context2 here

Student std1 = new Student()

{
1036

FirstName = "Pranaya",

LastName = "Rout"

};

context.Students.Add(std1);

UseExistingTransaction(context, transaction);

// If everything was successful, commit the transaction

transaction.Commit();

Console.WriteLine("Entities Added");

catch (Exception ex)

// If there was an error, roll back the transaction

transaction.Rollback();

throw;

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

}
1037

}
In the above Example:
 You begin by starting a transaction with the database directly.
 You pass this transaction to the UseExistingTransaction method, which tells
EF Core to use this transaction for all operations within the current context.
 You perform your database operations within the try block.
 After the operations, you either commit or roll back the transaction. Note that
this decision is taken outside of the UseExistingTransaction method.
 It is important to handle exceptions and ensure the transaction is rolled back
in case of an error.
 Finally, make sure to detach the context from the transaction once you’re
done by passing null to UseTransaction.
Choosing The Right Transaction in Entity Framework Core:
It’s important to choose the right approach based on the scope and requirements of your
transaction. The implicit transaction provided by SaveChanges() might be sufficient for
small, single-operation transactions, whereas more complex scenarios might require
BeginTransaction() or even TransactionScope. Always ensure that any manual transactions
are properly disposed of to avoid resource leaks, and remember that transactions should be
as short as possible to avoid locking resources for longer than necessary.
 Scalability and Performance: Nested or distributed transactions can impact
performance. TransactionScope may escalate to a distributed transaction if it
spans multiple connections, which is heavier on resources.
 Ease of Use: SaveChanges() within a DbContext is the simplest and most
recommended for basic operations.
 Flexibility: BeginTransaction() gives you more control and is recommended
when you need to span a transaction across multiple operations within a
single context or multiple instances of DbContext.
 Cross-Resource Transactions: TransactionScope is suitable for
transactions spanning multiple databases or resources like file systems.
 Compatibility: Not all database providers support TransactionScope or
distributed transactions.
 Error Handling: Make sure to handle exceptions properly to avoid partial
commits and ensure that transactions are rolled back in the event of an error.
Transaction Challenges in Entity Framework Core:
 Avoid Long-Running Transactions: Keeping transactions open for long
periods can lock resources and impact the performance of your application
and database.
 Handling Exceptions: Always ensure you handle exceptions properly to
avoid uncommitted transactions that can cause locks.
 Avoiding Ambient Transactions If Not Needed: TransactionScope can be
convenient but can lead to escalated transactions if multiple resource
managers get involved.
 Concurrency Considerations: Be aware of the database’s concurrency
model (optimistic/pessimistic) and how transactions are handled concurrently.
1038

Seed Data in Entity Framework Core (EF Core)


In this article, I will discuss Seed Data in Entity Framework Core (EF Core) with
Examples. Please read our previous article discussing Transactions in Entity Framework
Core (EF Core) with Examples.
What Do You Mean by Seed Data in Entity Framework Core?
Seeding data in Entity Framework Core refers to the process of populating a database with
initial data during the creation or migration of a database. This is particularly useful for
setting up a known data state for testing, development, or deploying an application for the
first time. Seed data can be useful for populating a database with a default data set during
development or after deployment, such as initial roles, administrative user accounts, or
master data.
Example to Understand How to Seed Data in EF Core:
Let us understand how to seed data into database tables using Entity Framework Core with
an example.
Define Your Model:
Before seeding data, first, we need to define the entity classes that make up our model. So,
create the Country, City, and State entities that will hold the country, state, and city master
data.
Ad
1/2
00:47

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem TV

using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreCodeFirstDemo.Entities

[Table("CountryMaster")]

public class Country

public int CountryId { get; set; }

public string CountryName { get; set; }

public string CountryCode { get; set; }

}
1039

[Table("StateMaster")]

public class State

public int StateId { get; set; }

public string StateName { get; set; }

public int CountryId { get; set; }

[Table("CityMaster")]

public class City

public int CityId { get; set; }

public string CityName { get; set; }

public int StateId { get; set; }

}
Add Seed Data to Your DbContext:
Inside your DbContext class, override the OnModelCreating method to specify the seed
data for your entities. You can specify seed data in the OnModelCreating method of your
DbContext class using the HasData method of the entity configuration. So, modify the
Context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)


1040

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

//Seeding Country Master Data using HasData method

modelBuilder.Entity<Country>().HasData(

new() { CountryId = 1, CountryName = "INDIA", CountryCode = "IND" },

new() { CountryId = 2, CountryName = "Austrailla", CountryCode = "AUS" }

);

//Seeding State Master Data using HasData method

modelBuilder.Entity<State>().HasData(

new() {StateId =1, StateName = "ODISHA", CountryId = 1 },

new() {StateId =2, StateName = "DELHI", CountryId = 1 }

);

//Seeding City Master Data using HasData method

modelBuilder.Entity<City>().HasData(

new() {CityId =1, CityName = "Bhubaneswar", StateId = 1 },

new() {CityId =2, CityName = "Cuttack", StateId = 1 }

);

}
1041

public DbSet<Country> CountryMaster { get; set; }

public DbSet<State> StateMaster { get; set; }

public DbSet<City> CityMaster { get; set; }

}
Create Migrations and Update the Database:
Once you have defined your seed data, you need to add a new migration that includes
these changes. Then, apply the migration to your database to insert the seed data. This can
be done by running the Update-Database command in the Package Manager Console, as
shown below:

Remember, seed data is inserted into the database only if the table is empty. If you add
new seed data and want to reapply all seeds, you must ensure the tables are in the
expected state or handle it programmatically.
Verify the Seed Data:
To verify the Seed Data, modify the Program class as follows:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.Data.SqlClient;

using Microsoft.EntityFrameworkCore;

using System.Data;

namespace EFCoreCodeFirstDemo

{
1042

public class Program

static async Task Main(string[] args)

try

using (EFCoreDbContext context = new EFCoreDbContext())

Console.WriteLine("Country Master:");

var Countries = context.CountryMaster.ToList();

foreach (var country in Countries)

Console.WriteLine($"\tCountry ID: {country.CountryId}, Name: {country.CountryName},


Code: {country.CountryCode}");

Console.WriteLine("State Master:");

var States = context.StateMaster.ToList();

foreach (var state in States)

Console.WriteLine($"\tState ID: {state.StateId}, Name: {state.StateName}");

Console.WriteLine("City Master:");

var Cities = context.CityMaster.ToList();

foreach (var city in Cities)


1043

Console.WriteLine($"\tCity ID: {city.CityId}, Name: {city.CityName}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Now run the application, and you should get the following output.

Considerations When Seeding Data in EF Core


 Idempotence: The seeding process is designed to be idempotent, which
means it won’t try inserting seed data if it already exists in the database. This
is achieved by checking the primary key values.
 Limitations: Seed data is best used for relatively static data that doesn’t
change often. It’s not suited for data that needs to be updated regularly.
1044

 Data Size: While there’s no strict limit on the amount of seed data, very large
data sets might be better served by a custom data import process outside of
EF Core migrations for performance reasons.
 Data Changes: If you need to change seed data after it has been added to
the database, you should create a new migration. Changing the HasData
method and applying a new migration will adjust the seeded data accordingly.
Custom Initialization for Seed Data in EF Core
In Entity Framework Core, seeding data typically involves specifying static data directly
within the OnModelCreating method of your DbContext using the HasData method.
However, in some scenarios, you may need more custom and dynamic data initialization,
which HasData does not directly support. For such cases, you can implement custom logic
to seed data in your application startup routine or as part of the EF Core migration process.
Custom initialization in Entity Framework Core allows you more control over the seeding
process, particularly when you have complex logic or large amounts of data to insert. This
typically involves executing code to explicitly insert data into the database rather than using
the HasData method, part of the model configuration.
Define the Seed Data Logic:
Create a method that contains the logic for seeding your database. This could be inside a
static class or part of your DbContext. So, create a static class named DbInitializer (you can
give any name) and then copy and paste the following code.
namespace EFCoreCodeFirstDemo.Entities

public static class DbInitializer

public static void Initialize(EFCoreDbContext context)

context.Database.EnsureCreated();

// Check if there are any Country or State or City already in the database

if (context.CountryMaster.Any() || context.StateMaster.Any() || context.CityMaster.Any())

// Database has been seeded

return;

else
1045

//Here, you need to Implement the Custom Logic to Seed the Master data

IList<Country> countries = new List<Country>();

Country IND = new() { CountryName = "INDIA", CountryCode = "IND" };

Country AUS = new() { CountryName = "Austrailla", CountryCode = "AUS" };

countries.Add(IND);

countries.Add(AUS);

context.CountryMaster.AddRange(countries);

IList<State> states = new List<State>();

State Odisha = new() { StateName = "ODISHA", CountryId = IND.CountryId };

State Delhi = new() { StateName = "DELHI", CountryId = IND.CountryId };

states.Add(Odisha);

states.Add(Delhi);

context.StateMaster.AddRange(states);

IList<City> cities = new List<City>();

City BBSR = new() { CityName = "Bhubaneswar", StateId = Odisha.StateId };

City CTC = new() { CityName = "Cuttack", StateId = Odisha.StateId };

cities.Add(BBSR);

cities.Add(CTC);

context.CityMaster.AddRange(cities);

context.SaveChanges();

}
1046

}
Modify the Context Class:
Next, modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Country> CountryMaster { get; set; }

public DbSet<State> StateMaster { get; set; }

public DbSet<City> CityMaster { get; set; }

}
Before proceeding further, let us delete the database and Migration folder and create the
Migration file again to update the database, as shown in the image below.
1047

Call the Seed Method:


Now, you need to call the seed method at the start of your application. This could be done
in the Main method, Startup class, or wherever it makes sense within your application’s
lifecycle. In our example, let us call this from the Main method of the Program class:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.Data.SqlClient;

using Microsoft.EntityFrameworkCore;

using System.Data;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using (EFCoreDbContext context = new EFCoreDbContext())

{
1048

//Call the Initialize Method to Seed the Data

DbInitializer.Initialize(context);

Console.WriteLine("Country Master:");

var Countries = context.CountryMaster.ToList();

foreach (var country in Countries)

Console.WriteLine($"\tCountry ID: {country.CountryId}, Name: {country.CountryName},


Code: {country.CountryCode}");

Console.WriteLine("State Master:");

var States = context.StateMaster.ToList();

foreach (var state in States)

Console.WriteLine($"\tState ID: {state.StateId}, Name: {state.StateName}");

Console.WriteLine("City Master:");

var Cities = context.CityMaster.ToList();

foreach (var city in Cities)

Console.WriteLine($"\tCity ID: {city.CityId}, Name: {city.CityName}");

Console.Read();

}
1049

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Handling Changes: If you update your seed method, you must handle those changes
manually, as they are not part of the EF Core migrations. You might need to write additional
logic to check what data is already present and what needs to be updated, inserted, or
deleted.
Considerations for Production: When deploying to production, you should be careful
about when and how you run your seed method. You wouldn’t want to overwrite or duplicate
data accidentally. Ensure that your seeding logic checks for existing data and only inserts
where appropriate.
Using Transactions: For larger seeding operations or when data integrity is critical, you
might use database transactions to ensure all seed data is committed or rolled back as a
single unit.
namespace EFCoreCodeFirstDemo.Entities

public static class DbInitializer

{
1050

public static void Initialize(EFCoreDbContext context)

using (var transaction = context.Database.BeginTransaction())

try

context.Database.EnsureCreated();

// Check if there are any Country or State or City already in the database

if (context.CountryMaster.Any() || context.StateMaster.Any() || context.CityMaster.Any())

// Database has been seeded

return;

else

//Here, you need to Implement the Custom Logic to Seed the Master data

IList<Country> countries = new List<Country>();

Country IND = new() { CountryName = "INDIA", CountryCode = "IND" };

Country AUS = new() { CountryName = "Austrailla", CountryCode = "AUS" };

countries.Add(IND);

countries.Add(AUS);

context.CountryMaster.AddRange(countries);

IList<State> states = new List<State>();

State Odisha = new() { StateName = "ODISHA", CountryId = IND.CountryId };


1051

State Delhi = new() { StateName = "DELHI", CountryId = IND.CountryId };

states.Add(Odisha);

states.Add(Delhi);

context.StateMaster.AddRange(states);

IList<City> cities = new List<City>();

City BBSR = new() { CityName = "Bhubaneswar", StateId = Odisha.StateId };

City CTC = new() { CityName = "Cuttack", StateId = Odisha.StateId };

cities.Add(BBSR);

cities.Add(CTC);

context.CityMaster.AddRange(cities);

context.SaveChanges();

transaction.Commit();

catch (Exception ex)

transaction.Rollback();

// Handle or log the exception

}
Considerations for Custom Seeding in EF Core:
1052

 Idempotency: Ensure that your seeding logic is idempotent. It should be safe


to run multiple times without causing duplicate data or other issues.
 Data Size: If seeding large amounts of data, consider performance
implications. Large data inserts can be resource-intensive.
 Data Complexity: Custom seeding methods can handle complex data
relationships or dynamic data more effectively than HasData.
 Environment Specific Data: You might want to seed different data based on
the environment (development, staging, production). Custom seeding
methods can provide the flexibility to achieve this.

Shadow Properties in Entity Framework Core


In this article, I will discuss Shadow Properties in Entity Framework Core (EF Core) with
Examples. Please read our previous article discussing Seed Data in Entity Framework
Core with Examples. Entity Framework Core introduced a new type of property called the
“Shadow” Property, which did not exist in EF 6.x.
1. What are Shadow Properties in EF Core?
2. Example to Understand Shadow Properties in Entity Framework Core
3. How Do We Check the Shadow Properties of an Entity in EF Core?
4. Creating Foreign Key Properties as Shadow Properties in EF Core
5. Shadow Property with Default Value in Entity Framework Core
6. When to use Shadow Properties in Entity Framework Core?
What are Shadow Properties in EF Core?
Shadow properties in Entity Framework Core are fields not defined in your .NET entity class
but are defined in the model and, hence, are part of the database schema. These properties
can be used to store and retrieve values with the database without declaring them explicitly
in your class model. Shadow properties are useful when working with database columns
that don’t have corresponding properties in our entity class.
This can be useful for fields that should be in the database but are not needed in the model
class, like audit information like who created or modified a row and when these operations
occurred. In our entities, we usually use fields like CreatedOn, LastModifiedOn, CreatedBy,
LastModifiedBy, etc., to store audit information. Shadow Properties are configured in the
OnModelCreating() method of the context class. For a better understanding, please have a
look at the following diagram:
1053

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:18 / 03:1710 Sec
As you can see in the above diagram, the Shadow Properties in EF Core are not part of our
entity classes. So, we cannot access these Shadow Properties as we access the other
properties of an entity. Shadow Properties can only be configured for an entity type while
building an Entity Data Model and mapped to a database column.
Example to Understand Shadow Properties in Entity Framework Core:
Shadow properties can be used for various purposes, such as:
 Auditing: Storing information like creation and modification timestamps.
 Concurrency Control: Managing concurrency with fields like timestamps or
version numbers.
 Tracking Additional information: Storing metadata or flags related to
entities.
Consider a real-time example of using shadow properties in an ASP.NET Core application
with Entity Framework Core. Imagine you have a blog post entity that wants to track when
each post was created and last updated, but you don’t want these audit fields to be part of
your domain model. Let us see how we can implement this using shadow Properties.
Define the BlogPost Entity Without Audit Properties:
Create a class file named BlogPost.cs and copy and paste the following code. Here, we
have not added the CreatedAt or LastUpdatedAt properties.
namespace EFCoreCodeFirstDemo.Entities

public class BlogPost

public int Id { get; set; }

public string Title { get; set; }


1054

public string Content { get; set; }

// No CreatedAt or LastUpdatedAt properties here

}
Configure Shadow Properties in the OnModelCreating Method of your
DbContext:
Shadow properties are typically defined in the OnModelCreating method of your DbContext
class using the Fluent API. So, to define a Shadow Property in EF Core, we can use the
ModelBuilder API in our DbContext class’s OnModelCreating method. You can use the
Property method to configure a shadow property. In our example, we have created two
Shadow Properties named CreatedAt and LastUpdatedAt for the BlogPost class. So,
modify the context class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<BlogPost>().Property<DateTime>("CreatedAt");

modelBuilder.Entity<BlogPost>().Property<DateTime>("LastUpdatedAt");
1055

public DbSet<BlogPost> BlogPosts { get; set; }

}
Set the Shadow Properties Values when Saving Changes:
We can do the same by overriding our context class’s SaveChanges() method, or while
defining the Shadow Property, we can provide the default value for the Shadow Property.
Let us first see how we can override the SaveChanges() method to set the Shadow
Properties Values in EF Core.
For a better understanding, please have a look at the following image. As you can see, we
have overridden the SaveChanges() method. Within the SaveChanges() method, we loop
through all the Entities whose Entity Type is BlogPost and check whether the entity state is
added or modified. If the Entity is in Added State, we set both CreatedAt and LastUpdatedAt
Shadow Properties values to the current date. If the Entity is in the Modified State, we set
the LastUpdatedAt Shadow Properties value to the current date.

So, modify the context class as follows:


using Microsoft.EntityFrameworkCore;

using System.Diagnostics;

using System.Threading.Channels;
1056

using System;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<BlogPost>().Property<DateTime>("CreatedAt");

modelBuilder.Entity<BlogPost>().Property<DateTime>("LastUpdatedAt");

public override int SaveChanges()

var timestamp = DateTime.UtcNow;

foreach (var entry in ChangeTracker.Entries<BlogPost>())

if (entry.State == EntityState.Added)

{
1057

entry.Property("CreatedAt").CurrentValue = timestamp;

entry.Property("LastUpdatedAt").CurrentValue = timestamp;

else if (entry.State == EntityState.Modified)

entry.Property("LastUpdatedAt").CurrentValue = timestamp;

return base.SaveChanges();

public DbSet<BlogPost> BlogPosts { get; set; }

}
Generate Migration and Apply Database Changes:
Open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.

Now, verify the database, and you should see the Shadow Properties in the table as shown
in the below image:
1058

Save BlogPosts with Shadow Properties:


When you add or update a BlogPost, Entity Framework Core will automatically handle the
shadow properties for us. Entity Framework Core will update the “CreatedAt” and
“UpdatedAt” shadow property values in the database. For a better understanding, please
modify the Program class as follows:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

var blogPost = new BlogPost

Title = "EF Core",


1059

Content = "IT is an ORM Framework"

};

using var context = new EFCoreDbContext();

context.BlogPosts.Add(blogPost);

context.SaveChanges();

Console.WriteLine("New BlogPost Added..");

// Entity Framework Core will set the "CreatedAt" and "LastUpdatedAt" Shadow Properties
Value

blogPost.Content = "Entity Framework Core is Updated";

context.SaveChanges();

// Entity Framework Core will update the "LastUpdatedAt" shadow property value.

Console.WriteLine("BlogPost Updated..");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

}
Output:

Now, verify the BlogPosts database table, and you should see the following with the
Shadow Properties:
1060

Querying the BlogPost Entity, Including the Shadow Properties:


Now, our BlogPost entity does not contain the Shadow Properties. Let us see how we can
retrieve the BlogPost data and the Shadow Properties. We can access the Shadow
Properties using the EF.Property function. For a better understanding, please modify the
Program class as follows:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

var blogPostsWithAudit = context.BlogPosts

.Select(bp => new

Title = bp.Title,

Content = bp.Content,

CreatedAt = EF.Property<DateTime>(bp, "CreatedAt"),

LastUpdatedAt = EF.Property<DateTime>(bp, "LastUpdatedAt")


1061

})

.ToList();

foreach (var blogPost in blogPostsWithAudit)

Console.WriteLine($"Title: {blogPost.Title}, CreatedAt: {blogPost.CreatedAt},


LastUpdatedAt:{blogPost.LastUpdatedAt}");

Console.WriteLine($"\tContent: {blogPost.Content}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

}
Output:

Note: In our example, the BlogPost class doesn’t contain CreatedAt or LastUpdatedAt
properties, but they are part of the BlogPosts table in the database. When we add or update
a BlogPost, the SaveChanges method sets these shadow properties automatically. When
querying, we can still retrieve these values even though they are not part of the BlogPost
class using the EF.Property method.
How Do We Check the Shadow Properties of an Entity in EF Core?
In Entity Framework Core, we can inspect an entity’s shadow properties at runtime using
the DbContext and its associated ChangeTracker. Here’s how you can check for shadow
properties:
 Retrieve the Entry for the Entity: Use the DbContext.Entry method to get
the EntityEntry for the entity you’re interested in.
1062

 Use the Metadata Property: The EntityEntry has a Metadata property that
provides access to metadata about the entity, including information on
shadow properties.
 Iterate Over Properties: You can then iterate over the
Metadata.GetProperties() collection and check for properties that are shadow
properties using the IsShadowProperty flag.
Here’s an example of how to write a method that prints out the names of all shadow
properties for a given entity:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

var blogPost = new BlogPost

Title = "EF Core",

Content = "IT is an ORM Framework"

};

using var context = new EFCoreDbContext();

context.BlogPosts.Add(blogPost);

context.SaveChanges();

// Assuming you have a DbContext instance named context

PrintShadowProperties(context, blogPost);
1063

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

public static void PrintShadowProperties<TEntity>(DbContext context, TEntity entity)


where TEntity : class

var entry = context.Entry(entity);

var shadowProperties = entry.Metadata.GetProperties()

.Where(p => p.IsShadowProperty())

.Select(p => p.Name);

Console.WriteLine($"Shadow Properties for {typeof(TEntity).Name}:");

foreach (var propName in shadowProperties)

Console.WriteLine(propName);

}
Output:
1064

Creating Foreign Key Properties as Shadow Properties in EF Core:


Let’s create an example where we have two entities, Blog and Post, and we will configure a
shadow property to act as a foreign key from Post to Blog without including the foreign key
property in the Post class. Here’s how you could set up the foreign key as a shadow
property in Entity Framework Core:
Define the Blog and Post entities without foreign key properties:
namespace EFCoreCodeFirstDemo.Entities

public class Blog

public int BlogId { get; set; }

public string Url { get; set; }

public List<Post> Posts { get; set; }

public class Post

public int PostId { get; set; }

public string Title { get; set; }

public string Content { get; set; }

// No BlogId foreign key property here

public Blog Blog { get; set; }

}
Configure the shadow foreign key property in the OnModelCreating method:
using Microsoft.EntityFrameworkCore;
1065

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<Post>()

.HasOne(p => p.Blog)

.WithMany(b => b.Posts)

.HasForeignKey("BlogId"); // Configure BlogId as a shadow property

public DbSet<Blog> Blogs { get; set; }

public DbSet<Post> Posts { get; set; }

}
Setting the shadow foreign key value when adding a new Post:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo
1066

public class Program

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

var blog = new Blog { Url = "http://dotnettutorials.net" };

context.Blogs.Add(blog);

context.SaveChanges();

var post = new Post { Title = "Hello World", Content = "Welcome to my Blog!" };

context.Posts.Add(post);

// Set the shadow foreign key value

context.Entry(post).Property("BlogId").CurrentValue = blog.BlogId;

context.SaveChanges();

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

}
1067

}
Using the shadow foreign key property in a query:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

var postsWithBlogs = context.Posts

.Select(p => new

Post = p,

BlogId = EF.Property<int>(p, "BlogId") // Access the shadow property

})

.ToList();

Console.Read();

catch (Exception ex)

{
1068

Console.WriteLine($"Error: {ex.Message}");

}
In this example, the Post class does not contain a BlogId property but is part of the Posts
table in the database as a shadow property. This allows us to have a clean domain model
without the foreign key property while still being able to set and retrieve the foreign key
value using EF Core functionalities.
Shadow Property with Default Value in EF Core
In Entity Framework Core, we can configure shadow properties with a default value using
the Fluent API. This can be useful for columns that should always start with a specific value
when a new record is created, such as a default state, creation date, or a flag. Here’s an
example of how to set up a shadow property with a default value:
Define your entity without the property you want as a shadow property:
namespace EFCoreCodeFirstDemo.Entities

public class User

public int UserId { get; set; }

public string Name { get; set; }

// IsActive is not included here, it will be a shadow property with a default value

}
Configure the shadow property and its default value in the OnModelCreating method:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext


1069

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

modelBuilder.Entity<User>().Property<bool>("IsActive").HasDefaultValue(true);

public DbSet<User> Users { get; set; }

}
In this example, we are creating a User entity and a corresponding IsActive shadow
property that isn’t defined in the User class but is expected to be in the database table. The
HasDefaultValue method sets the default value for the shadow property. In this case, when
a new User is added to the database if the IsActive value isn’t explicitly set, it will default to
true.
Adding a new User Without Setting the Shadow Property:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)


1070

try

using var context = new EFCoreDbContext();

var user = new User { Name = "Pranaya Rout" };

context.Users.Add(user);

context.SaveChanges();

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

}
In the above code, when the SaveChanges method is called, EF Core will insert a new User
with the IsActive property set to true by default in the database.
Querying the Shadow Property Value:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

{
1071

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

var usersWithStatus = context.Users

.Select(u => new

User = u,

IsActive = EF.Property<bool>(u, "IsActive") // Access the shadow property

})

.ToList();

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}");

}
This LINQ query includes the shadow property IsActive in the result set using
the EF.Property<T> method. This allows you to read the value of the shadow property
even though it’s not part of your entity class.
When to use Shadow Properties in Entity Framework Core?
Shadow properties in Entity Framework Core are useful in various scenarios:
1072

 Auditing: Shadow properties are often used for audit data, like when an
entity was created or last modified. This keeps audit details out from the
domain model but still in the database.
 Querying: You can query shadow properties using LINQ, but you must use
the EF.Property static method to refer to them in your queries.
 Migrations: When you add or change shadow properties, you must create a
new migration to update the database schema.
 Data Models and Mappings: Note that shadow properties are part of the EF
model, but they won’t be visible in your C# data models. This can sometimes
make it harder to understand the complete data model just by looking at the
classes.
 Model Clarity: While useful, overuse of shadow properties can lead to a less
transparent model, where significant parts of the data model are not
immediately visible in the code.
 Database Provider Compatibility: Ensure that any default values or
behaviors you set for shadow properties are compatible with your database
provider.
 Modeling Database Concerns: Sometimes, there are columns in a database
table that have no direct relevance to the domain model’s behavior or business
logic but are necessary for database operations or constraints. These can be
handled as shadow properties.
 Encapsulation: When you want to keep certain data private to the entity and
not expose it to the domain directly, shadow properties can be useful. They
allow the data to be stored and retrieved without defining it in the domain
model.
 Soft Delete: A shadow property can hold this flag in scenarios where you do
not delete records but mark them as deleted with a flag.

Global Query Filters in Entity Framework Core


In this article, I will discuss Global Query Filters in Entity Framework Core (EF
Core) with Examples. Please read our previous article discussing Shadow Properties in
Entity Framework Core with Examples. At the end of this article, you will understand the
following pointers:
1. What are Global Query Filters in Entity Framework Core?
2. Soft Delete Example in EF Core Using Global Query Filter
3. Bypassing the Global Query Filter in Entity Framework Core
4. Multi-Tenancy Example in EF Core Using Global Query Filter
5. Data Security Example in EF Core Using Global Query Filter
6. When to Use Global Query Filters in EF Core?
7. When Not to Use Global Query Filters in EF Core?
What are Global Query Filters in Entity Framework Core?
Global Query Filters in Entity Framework Core (EF Core) allow us to define query criteria
that are automatically applied to all queries across a particular entity type. This can be
1073

useful for things like soft delete functionality, multi-tenancy, or any scenario where you have
a common filtering logic that should be applied globally.
Key Points of Global Query Filters in Entity Framework Core:
 Definition: You can define Global Query Filters in the OnModelCreating
method of your DbContext class. It uses LINQ to specify the filter conditions.
This is done by using the HasQueryFilter method on an entity type builder.
 Automatic Application: Once the Global Query Filter is defined, the filter is
automatically applied to every LINQ query that retrieves data of the specified
entity type. This includes direct queries and indirect data retrievals, such as
navigation properties.
 Usage with LINQ Queries: When you use LINQ to query your entities, EF
Core automatically applies these filters, so you don’t need to specify the filter
conditions each time.
 Disabling Filters: In certain scenarios, you might want to bypass these filters
(e.g., for an admin role). EF Core allows us to ignore filters using the
IgnoreQueryFilters method in your queries.
 Parameterized Filters: EF Core also supports parameterized filters, allowing
you to pass parameters into your Global Query Filters. This is particularly
useful for scenarios like multi-tenancy, where the tenant ID might be
determined at runtime.
 Performance: While Global Query Filters can improve performance by
reducing the need for repeated filtering logic in queries, they can also
potentially impact performance if not used properly, as they add additional
criteria to all queries against the filtered entity types.
Common Uses of Global Query Filter in EF Core:
 Soft Delete: A common use case for Global Query Filters is implementing
soft delete functionality. Instead of physically deleting records from the
database, a flag (e.g., IsDeleted) is set. By using a filter
like builder.Entity<MyEntity>().HasQueryFilter(e => !e.IsDeleted);, you
can automatically exclude logically deleted entities from all queries of the
entity MyEntity.
 Multi-Tenancy: In a multi-tenant application, a filter can ensure that each
query only retrieves data belonging to the tenant associated with the current
user. For instance, you might filter entities based on a tenant ID.
 Security: Filtering data based on user roles or permissions.
Soft Delete Example in EF Core Using Global Query Filter
Let us see an example of implementing soft delete functionality using Global Query Filters
in Entity Framework Core (EF Core). The soft delete pattern marks entities as deleted
without removing them from the database.
Define the Entity with a Soft Delete Property:
First, you need an entity with a property to indicate whether it is deleted. This is typically a
boolean flag. So, create a class file named Product.cs and copy and paste the following
code. In our example, it is the IsDeleted property, which indicates whether a product is
deleted.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:23 / 03:1710 Sec
1074

namespace EFCoreCodeFirstDemo.Entities

public class Product

public int Id { get; set; }

public string Name { get; set; }

public bool IsDeleted { get; set; } // Soft delete flag

}
Configure the Global Query Filter in DbContext:
In your DbContext class, override the OnModelCreating method to define the global query
filter. This filter will automatically exclude entities marked as deleted in all queries. So,
modify the DbContext class as follows:
using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Logging;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//To Display the Generated SQL

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

//Configuring the Connection String


1075

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

base.OnModelCreating(modelBuilder);

// Global Query Filter for soft delete

modelBuilder.Entity<Product>().HasQueryFilter(e => !e.IsDeleted);

public DbSet<Product> Products { get; set; }

}
Generate Migration and Apply Database Changes:
Open the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give any name to your migration. Here, I am
giving Mig1. The name that you are giving it should not be given earlier.

We need demo data to check whether the Global Query Filter works. So, please execute
the following insert statement into the Product database tables. Please note that we have a
few records whose IsDeleted column value is set to false.
INSERT INTO Products VALUES ('Product-1', 1); -- 1 Means True, i.e., Deleted
1076

INSERT INTO Products VALUES ('Product-2', 0); -- 0 Means False, i.e., Not Deleted

INSERT INTO Products VALUES ('Product-3', 1); -- 1 Means True, i.e., Deleted

INSERT INTO Products VALUES ('Product-4', 0); -- 0 Means False, i.e., Not Deleted
Querying Entities:
When you query Products, the global query filter is automatically applied, and ‘deleted’
product entities are excluded from the result set. For a better understanding, please modify
the Main method of the Program class as follows:
using EFCoreCodeFirstDemo.Entities;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

var AllActiveProducts = context.Products.ToList(); // Returns only non-deleted entities

foreach (var product in AllActiveProducts)

Console.WriteLine($"ID:{product.Id}, Name:{product.Name}, IsDeleted:


{product.IsDeleted}");

Console.Read();

catch (Exception ex)


1077

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

As you can see in the above output, the SQL Select Query applies with the Global Query
Filter in the Where Clause to fetch only those not deleted records.
Bypassing the Global Query Filter in Entity Framework Core:
In cases where you need to access all entities, including those marked as deleted (like for
an admin view), you can bypass the global query filter in EF Core using
the IgnoreQueryFilters() method. For a better understanding, modify the Program class as
follows:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try
1078

using var context = new EFCoreDbContext();

var AllProducts = context.Products.IgnoreQueryFilters().ToList(); // Includes deleted entities

foreach (var product in AllProducts)

Console.WriteLine($"ID:{product.Id}, Name:{product.Name}, IsDeleted:


{product.IsDeleted}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Considerations: Add an index to the IsDeleted column for performance optimization,


especially if your table grows large.
1079

Multi-Tenancy Example in EF Core Using Global Query Filter:


Implementing multi-tenancy in Entity Framework Core using Global Query Filters involves
setting up your entities to be filtered based on a tenant identifier. This ensures that each
tenant can only access their own data. Let us see how we can implement this using Entity
Framework Core Using Global Query Filter:
Defining a Tenant Entity and Tenant Service
First, you might have a Tenant entity and a service to determine the current tenant based
on the user’s session, domain, or other criteria. Here, we have hardcoded the Tenant ID
value.
namespace EFCoreCodeFirstDemo.Entities

public class Tenant

public int TenantId { get; set; }

public string Name { get; set; }

// Other properties...

public interface ITenantService

int GetCurrentTenantId();

public class TenantService : ITenantService

public int GetCurrentTenantId()

// Implementation to retrieve the current tenant ID.

// ITenantService needs to identify the current tenant.

// This might involve parsing a subdomain, reading a cookie, or checking user credentials:
1080

return 1;

}
Modifying Your Entities to Include TenantId
Each entity that should be tenant-specific needs a TenantId field. Please modify the Product
class as follows:
namespace EFCoreCodeFirstDemo.Entities

public class Product

public int Id { get; set; }

public string Name { get; set; }

public int TenantId { get; set; }

}
Setting Up the DbContext:
Inject the ITenantService into your DbContext and use it in the OnModelCreating method to
apply the Global Query Filter. In your DbContext class, override the OnModelCreating
method to define the global query filter. This filter will automatically include entities based on
the TenantId.
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

private ITenantService _tenantService;

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)


1081

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

base.OnModelCreating(modelBuilder);

//You can Inject the TenantService instance via Constructor

_tenantService = new TenantService();

int currentTenantId = _tenantService.GetCurrentTenantId();

// Global Query Filter

modelBuilder.Entity<Product>()

.HasQueryFilter(e => e.TenantId == currentTenantId);

public DbSet<Product> Products { get; set; }

}
Next, generate migration and update the database. Once you update the database, execute
the following SQL Script.
INSERT INTO Products (Name, TenantId) VALUES ('Product-1', 1);

INSERT INTO Products (Name, TenantId) VALUES ('Product-2', 2);

INSERT INTO Products (Name, TenantId) VALUES ('Product-3', 2);

INSERT INTO Products (Name, TenantId) VALUES ('Product-4', 1);


1082

Querying Entities
When querying Products, the global filter is automatically applied. Modify the Program class
as follows and then verify the output:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

using var context = new EFCoreDbContext();

var TenantProducts = context.Products.ToList(); // Product Based on the Current TenantId

var AllTenantProducts = context.Products.IgnoreQueryFilters().ToList();

foreach (var product in TenantProducts)

Console.WriteLine($"ID:{product.Id}, Name:{product.Name}, TenantId:


{product.TenantId}");

Console.Read();

catch (Exception ex)

{
1083

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Considerations:
 Security: It’s important to ensure the tenant ID cannot be tampered with,
especially in multi-user or public-facing applications.
 Performance: Be careful of performance implications, especially if you have
many tenants or a large dataset.
 Indexing: Consider indexing the TenantId field for performance optimization.
Data Security Example in EF Core Using Global Query Filter
Implementing data security in Entity Framework Core using Global Query Filters involves
creating filters based on security levels or user roles. This ensures that users can only
access data they are authorized to see. Let’s go through a detailed example where we
implement a security level-based data access control:
Define a Security Level Property in Your Entities
Add a SecurityLevel property to each entity that needs to be secured. This property
indicates the required clearance level to access the entity. So, modify the Product class as
follows to include the SecurityLevel property.
namespace EFCoreCodeFirstDemo.Entities

public class Product

public int Id { get; set; }

public string Name { get; set; }

public int SecurityLevel { get; set; } // Security level of the entity

}
1084

Configure the Global Query Filter in DbContext


In your DbContext class, override the OnModelCreating method to include the global query
filter. This filter will ensure that users can only access data that matches or is below their
security clearance. So, modify the DbContext class as follows:
using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo.Entities

public class EFCoreDbContext : DbContext

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

//Configuring the Connection String

optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\
SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertifi
cate=True;");

protected override void OnModelCreating(ModelBuilder modelBuilder)

base.OnModelCreating(modelBuilder);

//You can inject this value based on the login user from sessions or cookies

//Here, we have hardcoded the value to 2

int _userSecurityLevel = 2;

// Global Query Filter for data security

modelBuilder.Entity<Product>().HasQueryFilter(e => e.SecurityLevel <=


_userSecurityLevel);

}
1085

public DbSet<Product> Products { get; set; }

}
Next, generate migration and update the database. Once you update the database, execute
the following SQL Script.
INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-1', 1);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-2', 2);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-3', 2);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-4', 1);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-5', 3);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-6', 3);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-7', 2);

INSERT INTO Products (Name, SecurityLevel) VALUES ('Product-8', 1);


Querying Entities
When querying SecureEntities, the global filter is automatically applied. Please modify the
Program class as follows and then verify the output:
using EFCoreCodeFirstDemo.Entities;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

{
1086

using var context = new EFCoreDbContext();

var ProductsBySecurityLevel = context.Products.ToList(); // Product Based on the Current


user SecurityLevel

var AllProducts = context.Products.IgnoreQueryFilters().ToList(); //All Products

foreach (var product in ProductsBySecurityLevel)

Console.WriteLine($"ID:{product.Id}, Name:{product.Name}, SecurityLevel:


{product.SecurityLevel}");

Console.Read();

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

When to Use Global Query Filters in EF Core?


1087

Global Query Filters in Entity Framework Core are powerful for applying automatic filtering
logic to all queries against a particular entity type. Here are scenarios when it’s particularly
useful to use them:
 Soft Delete: In applications where entities are not permanently deleted but
marked as inactive or deleted (soft delete), Global Query Filters can
automatically exclude these records from all queries.
 Multi-Tenancy: When building applications that serve multiple tenants (like
different companies or groups), Global Query Filters can ensure that each
tenant only accesses their own data by filtering based on tenant ID.
 Row-Level Security: Global Query Filters can enforce these rules if your
application has complex security requirements where users have access to
only certain rows of data (based on their role or department).
 Data Partitioning: For applications where data needs to be partitioned (like
based on region, department, etc.), Global Query Filters can automatically
segregate data accordingly in every query.
 Audit or Historical Data: In scenarios where you want to keep historical or
audit data in the same table but normally query only the current data, a
Global Query Filter can exclude historical records from standard queries.
 Common Conditions: If a common condition almost always applies to
queries of a particular entity (like only showing active users or products in
stock), a Global Query Filter can enforce this condition by default.
When Not to Use Global Query Filters in EF Core?
 Performance Considerations: If your filter conditions are complex and could
lead to performance issues, applying filters at the query level might be better.
 Global Impact: Remember that Global Query Filters impact every query
against the entity. If there are many scenarios where the filter needs to be
bypassed (using IgnoreQueryFilters), applying filters at the query level might
be better.
 Simplicity: For simpler applications or when filters are not consistently
required across all queries, applying filters directly in queries might be more
straightforward and maintainable.

Entity Framework Core Database First Approach


In this article, I will discuss the Entity Framework Core (EF Core) Database First
Approach with Examples. Please read our previous article discussing Global Query
Filters in Entity Framework Core with Examples.
Entity Framework Core Database First Approach
Entity Framework Core (EF Core) is a popular Object-Relational Mapping (ORM) framework
for .NET applications. It enables developers to work with a database using .NET objects,
eliminating the need for most of the data-access code developers usually need to write. EF
1088

Core supports two primary ways of developing a data access layer: Database-
First and Code-First. Here, we’ll focus on the Database-First approach.
The Entity Framework Core (EF Core) Database First approach is a technique where we
start with an existing database and use EF Core to create the data access layer of our
application. This approach is useful when working with a pre-existing database or when a
database administrator or a separate team leads the database design.
Steps to Implement EF Core Database-First Approach:
 Existing Database: You need an existing database to begin with. The
database can be designed and managed using any compatible database
management system like SQL Server, MySQL, Oracle, etc.
 Install EF Core Tools: Ensure you have the EF Core CLI tools or the EF
Core Power Tools (for Visual Studio) installed. These tools are essential for
generating the code from the database.
 Create a .NET Core Project: Start by creating a .NET Core project in which
you want to use EF Core. This could be any .NET Core project, like an
ASP.NET Core web application or a Console Application.
 Install EF Core NuGet Packages: Install the necessary EF Core NuGet
packages required for EF Core. You would typically
need Microsoft.EntityFrameworkCore and provider-specific packages
(like Microsoft.EntityFrameworkCore.SqlServer for SQL Server).
 Generate the Models: Use the EF Core CLI or Power Tools to scaffold the
DbContext and Entity Classes from your existing database. This is done using
the Scaffold-DbContext command. This command generates C# code that
represents the tables in your database as classes and properties.
 Use the DbContext: Once the DbContext and entities are scaffolded, you
can use them in your application to perform CRUD operations on the
database.
 Handling Database Changes: If the database schema changes, you need to
re-run the scaffolding command to update your models. Be aware that this
may overwrite any customizations you have made to the models.
 Migrations: Unlike the Code First approach, in Database First, migrations
are usually handled directly in the database using SQL scripts or other
database management tools rather than through EF Core.
 Testing and Deployment: Finally, test your application to ensure it works
correctly with the database, then deploy it.
Example to Understand EF Core Database First Approach:
Let us see an Example to Understand the EF Core Database First Approach with Multiple
Tables, Views, Stored Procedures, and Functions. Let us create a database in SQL Server
with the following:

Synthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video - Flycam Nem
TV00:12 / 03:1710 Sec
 Database: EFCoreDB:
 Table: Employees: This table should contain employee-related information.
 Table: Departments: This table should store department details.
 Views: EmployeeDetails: A view to aggregate and present detailed
employee information.
1089

 Stored Procedure: GetEmployeeById: Fetching employee data by their ID.


 Function: CalculateBonus: A function to calculate bonuses for employees.
So, please execute the following SQL Script to Create the data and required database
tables, views, stored procedures, and stored functions:
CREATE DATABASE EFCoreDB;

USE EFCoreDB;

-- Create Departments Database Table

-- This table will store department details.

-- It has an ID as the primary key and a name for the department.

CREATE TABLE Departments (

DepartmentID INT PRIMARY KEY IDENTITY,

DepartmentName NVARCHAR(100) NOT NULL

);

GO

-- Create Employees Database Table

-- This table will store employee details.

-- It includes an ID as the primary key, personal details like name and email,

-- and a foreign key to the Departments table.

CREATE TABLE Employees (

EmployeeID INT PRIMARY KEY IDENTITY,

FirstName NVARCHAR(50) NOT NULL,

LastName NVARCHAR(50) NOT NULL,

Email NVARCHAR(100),

Salary INT,

DepartmentID INT,

FOREIGN KEY (DepartmentID) REFERENCES Departments(DepartmentID)


1090

);

GO

-- Create EmployeeDetails View

-- Joins data from the Employees and Departments tables to EmployeeDetails View

CREATE VIEW EmployeeDetails AS

SELECT

e.EmployeeID,

e.FirstName,

e.LastName,

e.Email,

e.Salary,

d.DepartmentName

FROM Employees e

INNER JOIN Departments d

ON e.DepartmentID = d.DepartmentID;

GO

-- Create GetEmployeeById Stored Procedure

-- This will return the Employee Details based on the EmployeeID

CREATE PROCEDURE GetEmployeeById

@EmployeeID INT

AS

BEGIN

SELECT

e.EmployeeID,
1091

e.FirstName,

e.LastName,

e.Email,

e.Salary,

e.DepartmentID,

d.DepartmentName

FROM Employees e

INNER JOIN Departments d

ON e.DepartmentID = d.DepartmentID

WHERE e.EmployeeID = @EmployeeID;

END;

GO

-- Create CalculateBonus Stored Function

-- This will calculate and return the Bonus of the Employee Based on the EmployeeID

CREATE FUNCTION CalculateBonus

@EmployeeID INT

RETURNS DECIMAL(12, 2)

AS

BEGIN

DECLARE @Salary DECIMAL(12, 2);

DECLARE @Bonus DECIMAL(12, 2);

-- Assuming there is a Salary column in Employees table


1092

SELECT @Salary = Salary FROM Employees WHERE EmployeeID = @EmployeeID;

-- Calculate the bonus which is 25% of the Salary

SET @Bonus = @Salary * 25 / 100;

RETURN @Bonus;

END;
At this point, your database structure should be as shown in the image below:

Creating a New Console Application:


You can use EF Core Database First Approach with any Dot Net Core Applications,
including ASP.NET Core MVC, Web API, Console Application, etc. So, let us create a new
1093

Console Application. Once you create the Console Application, please add the following two
packages from NuGet, which are required for EF Core.
 Microsoft.EntityFrameworkCore.SqlServer
 Microsoft.EntityFrameworkCore.Tools
Creating and implementing DB Context
Now, we will see how to create the Context and Entity classes from our existing EFCoreDB
database in Entity Framework Core. Creating Context and Entity classes for an existing
database is called Database-First Approach. Entity Framework Core does not support
Visual Designer for DB Model and Wizard to create the entity and context classes similar to
Entity Framework 6. Here, we need to use the Scaffold-DbContext command.
This Scaffold-DbContext command creates entity and context classes based on the
schema of the existing database, and we need to do this using Package Manager Console
(PMC) tools.
Understanding Scaffold-DbContext Command in EF Core:
The Scaffold-DbContext command in Entity Framework Core (EF Core) is a powerful
feature used for reverse engineering. It essentially generates entity and context classes
based on an existing database, allowing developers to work with a database using EF
Core’s object-relational mapping capabilities.
Purpose: The primary purpose of Scaffold-DbContext is to create a set of classes that
represent the tables and relationships in an existing database. This allows developers to
interact with the database using strongly typed objects rather than writing raw SQL queries.
Usage: This command is used within the Package Manager Console in Visual Studio or via
the .NET Core CLI (Command Line Interface). It requires a few parameters to be specified:
 Connection String: The database connection string tells EF Core how to
connect to the database.
 Provider: Specifies the EF Core database provider to use. This is typically
the NuGet package corresponding to the database used (e.g.,
Microsoft.EntityFrameworkCore.SqlServer for SQL Server).
 Options (Optional): Additional options include specifying specific tables to
scaffold, the directory where the classes should be created, the context’s
namespace, and whether to use data annotations or Fluent API for
configuration.
Result: After running the command, EF Core generates several classes in your project:
 DbContext Class: This class derives from DbContext and includes
properties (DbSets) for each table.
 Entity Classes: Each table in the database corresponds to an entity class
with properties that map to the columns in the table.
Customization: The scaffolded classes can be modified to suit the needs of your
application. However, it’s important to note that changes to these classes may be
overwritten if you re-run the command.
Updating Models: If the database schema changes, the EF Core models need to be
updated. This can be done by re-running Scaffold-DbContext with the -Force option to
overwrite the existing models. However, as mentioned, this will overwrite any
customizations.
Syntax: Scaffold-DbContext “Your_Connection_String”
Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Employees,
Departments -DataAnnotations
1094

Example: Scaffold-Dbcontext “Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;


Database=EFCoreDB; Trusted_Connection=True; TrustServerCertificate=True;”
Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
Now, let us proceed and execute the above Scaffold-DbContext Command using the
Package Manager Console. Open the Package Manager Console, type Scaffold-
Dbcontext “Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV; Database=EFCoreDB;
Trusted_Connection=True; TrustServerCertificate=True;”
Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models command, select the
project where you want to execute this command, then press the enter button as shown in
the below image.

Once you press the Enter button, if everything is provided correctly, you will get the
following message.

The entity classes and DBContext class will be created in the Models folder, as shown in
the image below.

Now, let us Proceed and see how we can perform the database CRUD Operations using
the Entity Framework Core Database First Approach. Later, I will show how to use the
Views, Stored Procedures, and Stored Functions.
CRUD Operations Using Database First Approach:
Modify the Main method of the Program class as follows. The following example performs
the database CRUD Operations using the EF Core Database First Approach:
1095

using EFCoreCodeFirstDemo.Models;

namespace EFCoreCodeFirstDemo

public class Program

static async Task Main(string[] args)

try

//Create an Instance of DbContext

EfcoreDbContext context = new EfcoreDbContext();

//Adding Departments to the Departments database table

//We are adding two Departments IT and HR

Department ITDepartment = new()

DepartmentName = "IT",

};

context.Add(ITDepartment);

Department HRDepartment = new()

DepartmentName = "HR",

};

context.Add(HRDepartment);

context.SaveChanges();
1096

Console.WriteLine("\nIT and HR Departments Added...");

//Create Operations

//Adding Few Employees to the Employees Database Table

var employee1 = new Employee { FirstName = "Pranaya", LastName = "Rout", Salary =


550000, Email = "A@Example.com", DepartmentId = ITDepartment.DepartmentId };

context.Employees.Add(employee1);

var employee2 = new Employee { FirstName = "Tarun", LastName = "Kumar", Salary =


650000, Email = "B@Example.com", DepartmentId = ITDepartment.DepartmentId };

context.Employees.Add(employee2);

var employee3 = new Employee { FirstName = "Hina", LastName = "Sharma", Salary =


750000, Email = "C@Example.com", DepartmentId = HRDepartment.DepartmentId };

context.Employees.Add(employee3);

context.SaveChanges();

Console.WriteLine("\nEmployees Pranaya, Tarun and Hina Added...");

//Read Operations

//Display All the Employees Details

Console.WriteLine("\nDisplaying All the Employees Details");

var employees = context.Employees.ToList();

foreach (var emp in employees)

Console.WriteLine($"\tName: {emp.FirstName} {emp.LastName}, Salary: {emp.Salary},


Department: {emp.Department?.DepartmentName}");

//Update Operation

//Update the Salary, Last name and Department of First Employee


1097

Console.WriteLine("\nUpdating Salary, Last name and Department of First Employee");

employee1.LastName = "Parida";

employee1.Salary = 900000;

employee1.DepartmentId = HRDepartment.DepartmentId;

context.SaveChanges();

Console.WriteLine("After Updation");

Console.WriteLine($"Name: {employee1.FirstName} {employee1.LastName}, Salary:


{employee1.Salary}, Department: {employee1.Department?.DepartmentName}");

//Delete Operation

//Delete the Third Employee

Console.WriteLine("\nRemoving Third Employee");

context.Remove(employee3);

context.SaveChanges();

Console.WriteLine("After Remove, All Employees");

employees = context.Employees.ToList();

foreach (var emp in employees)

Console.WriteLine($"\tName: {emp.FirstName} {emp.LastName}, Salary: {emp.Salary},


Department: {emp.Department?.DepartmentName}");

Console.Read();

catch (Exception ex)

{
1098

Console.WriteLine($"Error: {ex.Message}"); ;

}
Output:

Now, let us proceed and see how we can use the Views, Stored Procedures, and Functions
in Entity Framework Core Database First Approach:
Using Views and Stored Procedures in EF Core DB First Approach:
Please modify the Main Method of the Program class as follows: I am showing how to use
the Stored procedure and views with Entity Framework Core Database First Approach using
the FromSqlRaw method.
using EFCoreCodeFirstDemo.Models;

using Microsoft.Data.SqlClient;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo

public class Program


1099

static async Task Main(string[] args)

try

//Create an Instance of DbContext

EfcoreDbContext context = new EfcoreDbContext();

Console.WriteLine("Using View");

var emp = context.EmployeeDetails.FirstOrDefault(emp => emp.EmployeeId == 1);

Console.WriteLine($"\tName: {emp?.FirstName} {emp?.LastName}, Salary: {emp?.Salary},


Department: {emp?.DepartmentName}");

//Executing Stored Procedure (GetEmployeeById):

//As the Stored Procedure Returning an Emloyee Details, we can use FromSqlRaw Method

Console.WriteLine("Using Stored Procedure");

var employeeIdParameter = new SqlParameter("@EmployeeID", 1);

var employee = context.Employees

.FromSqlRaw("EXEC GetEmployeeById @EmployeeID", employeeIdParameter)

.AsEnumerable()

.FirstOrDefault();

Console.WriteLine($"\tName: {employee?.FirstName} {employee?.LastName}, Salary:


{employee?.Salary}, Department: {employee?.Department?.DepartmentName}");

Console.Read();

catch (Exception ex)


1100

Console.WriteLine($"Error: {ex.Message}"); ;

}
Note: The most important point you need to remember is while using
the FromSqlRaw method, it will map the result to the specified Entity type, and it is
mandatory to return the required columns in the Stored Procedures or from the Views.
Otherwise, the mapping will not be done, and it will throw a Runtime Exception.
Calling Stored Function using EF Core:
Even though we are using a Database-First approach, we need to manually define the
function in our DbContext class, as EF Core does not automatically import functions through
the scaffolding process. So, add the following method in your DbContext that represents the
scalar function. Use the DbFunction attribute to link it to the SQL function.
//Specify the database Function name and database schema

[DbFunction("CalculateBonus", Schema = "dbo")]

public static decimal CalculateBonus(int EmployeeID)

// The actual implementation is in the SQL Server function.

// This method is for EF Core to know how to call the function.

throw new NotImplementedException();

}
Once you have defined the function in your DbContext class, you can call it like any other
static method in your code. So, modify the Program class as follows:
using EFCoreCodeFirstDemo.Models;

using Microsoft.Data.SqlClient;

using Microsoft.EntityFrameworkCore;

namespace EFCoreCodeFirstDemo
1101

public class Program

static async Task Main(string[] args)

try

//Don't call the CalculateBonus Method as follows

//decimal bonus = EfcoreDbContext.CalculateBonus(EmployeeID);

//Create an Instance of DbContext

EfcoreDbContext context = new EfcoreDbContext();

//Using CalculateBonus method inside a LINQ Query

var employee = context.Employees

.Where(x => x.EmployeeId == 1)

.Select(d => new

Name = d.FirstName + " "+ d.LastName,

Salary = d.Salary,

DepartmentName = d.Department.DepartmentName,

Bonus = EfcoreDbContext.CalculateBonus(d.EmployeeId)

}).FirstOrDefault();

Console.WriteLine($"Name: {employee?.Name}, Salary: {employee?.Salary}, Bonus:


{employee?.Bonus}, Department: {employee?.DepartmentName}");

Console.Read();
1102

catch (Exception ex)

Console.WriteLine($"Error: {ex.Message}"); ;

CRUD Operations in ASP.NET Core MVC using Entity Framework Core


In this article, I will discuss How to Perform Database CRUD Operations in ASP.NET
Core MVC Web Application using Entity Framework Core (EF Core Code First)
Approach with Multiple Database tables. Please read our Entity Framework Basics article
series before proceeding to this article.
CRUD Operations in ASP.NET Core MVC using EF Core
CRUD (Create, Read, Update, Delete) operations are fundamental for most web
applications, and ASP.NET Core MVC with Entity Framework Core provides a streamlined
approach for implementing these operations.
Let us see one real-time example of performing database CRUD operations in an ASP.NET
Core MVC Application using Entity Framework Core with multiple database tables. Let us
create one complete example of managing employees and departments in an organization.
In this example, we will use ASP.NET Core MVC, Entity Framework Core Code First
Approach, and SQL Server Database.
Step 1: Project Setup
Create a New ASP.NET Core MVC Project: Open Visual Studio. Create a new project
(CRUDinCoreMVC) and select the ASP.NET Core Web App (Model-View-Controller)
template.

NextStaySynthesize Beautiful Scenes of Ha Giang Via Super Quality Travel Video -


Flycam Nem TV00:29 / 03:1710 Sec
Install The Packages: Once you have created the Project, as we are going to work with the
SQL Server Database, we need to install the following two NuGet Packages, which are
required for Entity Framework Core:
Microsoft.EntityFrameworkCore.SqlServer:
1103

This package is the Entity Framework Core database provider for Microsoft SQL Server and
Azure SQL Database. It is necessary for any ASP.NET Core application that intends to use
these databases. The primary functions of this package include:
 Database Connection: It allows EF Core to establish connections to a SQL
Server database.
 SQL Generation: It translates the LINQ queries from your application into
SQL queries that the SQL Server can understand.
 Schema Generation: It is responsible for translating the entity data models
into SQL Server database schemas. This is particularly useful when creating
and migrating databases.
 Optimizing Performance: The package includes SQL Server-specific
optimizations, enhancing the performance of data access operations.
Microsoft.EntityFrameworkCore.Tools:
This package provides additional tools for working with Entity Framework Core, which is
especially useful during development. These tools enhance the development experience
and simplify many database-related tasks. Key features include:
 Migrations: Commands for creating and managing migrations are included,
which help evolve the database schema over time without losing data.
 Database Update: This tool allows applying migrations to update the
database schema directly from the command line or via the Package
Manager Console in Visual Studio.
 Database Scaffolding: It can reverse engineer a database schema to create
entity model classes and a DbContext based on an existing database, which
is particularly useful in database-first scenarios.
 Script Generation: Generate SQL scripts from migrations, which can be
useful for manual reviews or when deploying databases through scripts.
You can install the above two Packages using NuGet Package Manager for Solution or by
using Package Manager Console. Please execute the following command using the
Package Manager Console to install the Packages.
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Your project structure should look like the one shown below after Step 1. However, your
package might be different when you read this article.
1104

Step 2: Define Models


Next, we need to define the models according to our application’s data structure. As we will
manage the Employee and Department data, let’s define two models: Employee and
Department. So, create a class file named Department.cs within the Models folder and
then copy and paste the following code:
namespace CRUDinCoreMVC.Models

public class Department

public int DepartmentId { get; set; }

public string Name { get; set; }

public List<Employee> Employees { get; set; }

}
Create another class file named Employee.cs within the Models folder, and then copy and
paste the following code:
1105

namespace CRUDinCoreMVC.Models

public class Employee

public int EmployeeId { get; set; }

public string Name { get; set; }

public string Email { get; set; }

public string Position { get; set; }

public int DepartmentId { get; set; }

public Department? Department { get; set; }

}
By default, we have implemented one-to-many relationships between Employees and
Departments. An employee belongs to a single department, and one department can have
many employees.
Step 3: Configure the Database Connection
Instead of hard-coding the connection string with the DbContext class, we will store the
connection string in the appsettings.json file. So, add your database connection string in
the appsettings.json file as follows:
{

"Logging": {

"LogLevel": {

"Default": "Information",

"Microsoft.AspNetCore": "Warning"

},

"AllowedHosts": "*",
1106

"ConnectionStrings": {

"EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\
SQLSERVER2022DEV;Database=EFCoreMVCDB;Trusted_Connection=True;TrustServerC
ertificate=True;"

}
Step 4: Configure DbContext
Create a DbContext class for the application to manage the database. In Entity Framework
Core (EF Core), the DbContext class is the component that serves as a bridge between
your application’s code and the database. It plays an important role in managing the
interactions with the underlying database in an efficient and performance-oriented manner.
So, add a class named EFCoreDBContext.cs and then copy and paste the following code.
using Microsoft.EntityFrameworkCore;

using System.Diagnostics.Metrics;

namespace CRUDinCoreMVC.Models

public class EFCoreDbContext : DbContext

//Constructor calling the Base DbContext Class Constructor

public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)

//OnConfiguring() method is used to select and configure the data source

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

protected override void OnModelCreating(ModelBuilder modelBuilder)


1107

//Adding Domain Classes as DbSet Properties

public DbSet<Employee> Employees { get; set; }

public DbSet<Department> Departments { get; set; }

}
Role of DbContext in EF Core:
 Querying: DbContext provides the necessary methods and properties to
query the database. It converts LINQ (Language Integrated Query)
expressions into SQL queries that the database can understand.
 Saving Changes: It tracks changes made to objects and applies them to the
database when SaveChanges() or SaveChangesAsync() is called. This
includes translating the changes into insert, update, or delete commands.
 Model Mapping: It maps classes to database tables and properties to table
columns through the ModelBuilder class used in the OnModelCreating
method.
 Change Tracking: DbContext tracks entities’ states during their lifecycle.
This tracking ensures that only actual changes are updated in the database
during a save operation, which helps optimize database access and improve
performance.
Step 5: Configure the Database Connection:
Next, we need to configure the connection string with the DbContex class. Please add the
following code to the Program class. The following code configures the DbContext in an
ASP.NET Core application using Entity Framework Core (EF Core).
//Configure the ConnectionString and DbContext class

builder.Services.AddDbContext<EFCoreDbContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection"))
;

});
Code Explanation:
 builder.Services: builder.Services refer to the IServiceCollection provided by
the ASP.NET Core host builder. This collection registers services that the
1108

application will use, including platform features like MVC, logging, DI


containers, and more.
 AddDbContext<EFCoreDbContext>: The AddDbContext is an extension
method provided by EF Core that registers the DbContext as a service in the
DI (Dependency Injection) container. In this case, it’s registering
EFCoreDbContext. This method also ensures that the lifecycle of the
DbContext is managed correctly, typically as a scoped service. This means a
new instance of the DbContext is created for each request.
 Lambda Configuration (options => …): The lambda expression is used to
configure options for the DbContext. These options control how the
DbContext behaves and interacts with the underlying database.
 options.UseSqlServer: UseSqlServer is a method that specifies SQL Server
as the database provider for EF Core. This method also tells EF Core to
translate the LINQ queries and other data operations into SQL that is
compatible with SQL Server.
 builder.Configuration.GetConnectionString(“EFCoreDBConnection”): bu
ilder.Configuration provides access to the application’s configuration, typically
including settings from files like appsettings.json, environment variables, and
other configuration sources. GetConnectionString is a method that retrieves a
connection string by its key (“EFCoreDBConnection” in this case) from the
application’s configuration. This connection string contains the necessary
information for connecting to the SQL Server database (like server address,
database name, credentials, etc.).
Step 6: Database Migration
Next, we need to generate the EF Core migrations and update the database schema. Open
the Package Manager Console and Execute the add-migration and update-
database commands as follows. You can give your migration any name. Here, I am giving it
EFCoreDBMig1. The name you are giving it should not be given earlier.

With this, our Database with Departments and Employees table is created, as shown in the
below image:
1109

Before proceeding and performing the database CRUD Operations, let us first insert some
master data into the Departments database table by executing the following INSERT SQL
statements, which we will use while performing the Employee CRUD operation.
INSERT INTO Departments VALUES ('IT');

INSERT INTO Departments VALUES ('HR');

INSERT INTO Departments VALUES ('Payroll');


Step 7: Creating EmployeesController to Perform CRUD Operations
Using EF Core:
Next, create an Empty MVC Controller named EmployeesController within
the Controllers folder. Here, I am going to Scaffold Controllers and Views, which will
automatically generate the Actions and Views using the Entity Framework Core for us to
perform the CRUD Operations. Later, we will modify the auto-generated actions and views
as per our requirements. Please follow the below steps to Scaffold Controllers and Views.
Right-click on the Controllers folder and then select Add => Controller from the context
menu, which will open the following Add New Scaffold Item window. Here, please
select MVC Controller with views, using Entity Framework option and then click on the
Add button as shown in the image below:
1110

Once you click on the Add button, the following window will open. Here, provide the Model
Class as Employee, provide the DbContext Class as EFCoreDBContext, Keep the rest
of the setting for Views as it is, provide the Controller name as EmployeesController, and
then click on the Add button as shown in the below image:

Once you click the Add button, it will take some time to create the controller, all the action
methods to perform the database CRUD Operations, and the corresponding views for us.
The following is the auto-generated Employees Controller class:
using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Rendering;

using Microsoft.EntityFrameworkCore;

using CRUDinCoreMVC.Models;

namespace CRUDinCoreMVC.Controllers
1111

public class EmployeesController : Controller

private readonly EFCoreDbContext _context;

public EmployeesController(EFCoreDbContext context)

_context = context;

// GET: Employees

public async Task<IActionResult> Index()

var eFCoreDbContext = _context.Employees.Include(e => e.Department);

return View(await eFCoreDbContext.ToListAsync());

// GET: Employees/Details/5

public async Task<IActionResult> Details(int? id)

if (id == null || _context.Employees == null)

return NotFound();

var employee = await _context.Employees

.Include(e => e.Department)

.FirstOrDefaultAsync(m => m.EmployeeId == id);


1112

if (employee == null)

return NotFound();

return View(employee);

// GET: Employees/Create

public IActionResult Create()

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"DepartmentId");

return View();

// POST: Employees/Create

// To protect from overposting attacks, enable the specific properties you want to bind to.

// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.

[HttpPost]

[ValidateAntiForgeryToken]

public async Task<IActionResult>


Create([Bind("EmployeeId,Name,Email,Position,DepartmentId")] Employee employee)

if (ModelState.IsValid)

_context.Add(employee);
1113

await _context.SaveChangesAsync();

return RedirectToAction(nameof(Index));

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"DepartmentId", employee.DepartmentId);

return View(employee);

// GET: Employees/Edit/5

public async Task<IActionResult> Edit(int? id)

if (id == null || _context.Employees == null)

return NotFound();

var employee = await _context.Employees.FindAsync(id);

if (employee == null)

return NotFound();

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"DepartmentId", employee.DepartmentId);

return View(employee);

// POST: Employees/Edit/5
1114

// To protect from overposting attacks, enable the specific properties you want to bind to.

// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.

[HttpPost]

[ValidateAntiForgeryToken]

public async Task<IActionResult> Edit(int id,


[Bind("EmployeeId,Name,Email,Position,DepartmentId")] Employee employee)

if (id != employee.EmployeeId)

return NotFound();

if (ModelState.IsValid)

try

_context.Update(employee);

await _context.SaveChangesAsync();

catch (DbUpdateConcurrencyException)

if (!EmployeeExists(employee.EmployeeId))

return NotFound();

}
1115

else

throw;

return RedirectToAction(nameof(Index));

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"DepartmentId", employee.DepartmentId);

return View(employee);

// GET: Employees/Delete/5

public async Task<IActionResult> Delete(int? id)

if (id == null || _context.Employees == null)

return NotFound();

var employee = await _context.Employees

.Include(e => e.Department)

.FirstOrDefaultAsync(m => m.EmployeeId == id);

if (employee == null)

return NotFound();
1116

return View(employee);

// POST: Employees/Delete/5

[HttpPost, ActionName("Delete")]

[ValidateAntiForgeryToken]

public async Task<IActionResult> DeleteConfirmed(int id)

if (_context.Employees == null)

return Problem("Entity set 'EFCoreDbContext.Employees' is null.");

var employee = await _context.Employees.FindAsync(id);

if (employee != null)

_context.Employees.Remove(employee);

await _context.SaveChangesAsync();

return RedirectToAction(nameof(Index));

private bool EmployeeExists(int id)

return (_context.Employees?.Any(e => e.EmployeeId == id)).GetValueOrDefault();

}
1117

}
Now, if you verify the Views folder, then you will see the views for the Employees controller
as shown in the below image:

Note: The Scaffolded Controllers will contain methods for CRUD operations. As we
progress, we will customize these methods and views, as per our application requirements.
Creating Department Controller:
The way we have created the EmployeesController, in the same way, we can also create
the DepartmentsController. So, please follow the same steps and create the Departments
Controller. While creating the Controller, you must provide the Model class as Department.
Testing
Run the application and test all CRUD operations for both employees and departments.
Ensure that the department selection works correctly when creating or editing an
employee. Before testing, first, modify the Default controller and action to Employee and
Index in the Program class as follows:
app.MapControllerRoute(

name: "default",

pattern: "{controller=Employees}/{action=Index}/{id?}");
Now, if you run the application and go to the Employees/Create URL, you will see it
displays the Department ID in the Drop-Down List instead of the Department name, as
shown in the image below.
1118

To display the Department name instead of ID, modify the Create Action Method (both Get
and Post) of the EmployeesController as follows:
// GET: Employees/Create

public IActionResult Create()

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"Name");

return View();

// POST: Employees/Create

// To protect from overposting attacks, enable the specific properties you want to bind to.

// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.

[HttpPost]
1119

[ValidateAntiForgeryToken]

public async Task<IActionResult>


Create([Bind("EmployeeId,Name,Email,Position,DepartmentId")] Employee employee)

if (ModelState.IsValid)

_context.Add(employee);

await _context.SaveChangesAsync();

return RedirectToAction(nameof(Index));

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"Name", employee.DepartmentId);

return View(employee);

}
Now, run the application and navigate to the Employees/Create URL, and you should see
it showing the Department name in the dropdown list. Let us create one employee and click
the Create button, as shown in the image below.
1120

Once you click on the Create button, the new employee will be added to the database.
Then, it will redirect you to the Index page, which will display all the Employees, as shown in
the image below. We have created only one employee, and that employee’s information will
be displayed here.

If you look at the Index view, it is showing the Department as 2. So, instead of showing the
Department ID, we need to show the Department name. To do so, modify the Index view of
the Employees controller as follows:
@model IEnumerable<CRUDinCoreMVC.Models.Employee>

@{

ViewData["Title"] = "Index";
1121

<h1>Index</h1>

<p>

<a asp-action="Create">Create New</a>

</p>

<table class="table">

<thead>

<tr>

<th>

@Html.DisplayNameFor(model => model.Name)

</th>

<th>

@Html.DisplayNameFor(model => model.Email)

</th>

<th>

@Html.DisplayNameFor(model => model.Position)

</th>

<th>

@Html.DisplayNameFor(model => model.Department)

</th>

<th></th>

</tr>

</thead>

<tbody>
1122

@foreach (var item in Model)

<tr>

<td>

@Html.DisplayFor(modelItem => item.Name)

</td>

<td>

@Html.DisplayFor(modelItem => item.Email)

</td>

<td>

@Html.DisplayFor(modelItem => item.Position)

</td>

<td>

@if (item.Department != null)

@Html.DisplayFor(modelItem => item.Department.Name)

</td>

<td>

<a asp-action="Edit" asp-route-id="@item.EmployeeId">Edit</a> |

<a asp-action="Details" asp-route-id="@item.EmployeeId">Details</a> |

<a asp-action="Delete" asp-route-id="@item.EmployeeId">Delete</a>

</td>

</tr>
1123

</tbody>

</table>
Now, run the application, and it should display the Department name in the Index view as
shown in the below image:

To see the Employee details, click the Details button as shown in the above image. Once
you click on the Details button, it will open the following Details view.

As you can see, the Department ID is also displayed here. To show the Department name
instead of the Department ID, please modify the Details view of the Employee controller as
follows:
@model CRUDinCoreMVC.Models.Employee

@{

ViewData["Title"] = "Details";

<h1>Details</h1>

<div>

<h4>Employee</h4>
1124

<hr />

<dl class="row">

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Name)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Name)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Email)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Email)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Position)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Position)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Department)

</dt>

<dd class = "col-sm-10">


1125

@if (Model.Department != null)

@Html.DisplayFor(model => model.Department.Name)

</dd>

</dl>

</div>

<div>

<a asp-action="Edit" asp-route-id="@Model?.EmployeeId">Edit</a> |

<a asp-action="Index">Back to List</a>

</div>
Now, run the application and see the Details of the Employee and it should show the
Department name as expected, as shown in the image below:

Now, click the Edit button either from the Details view or Index view to edit an employee.
Once you click the Edit button, the following view with prepopulated employee information
will open.
1126

Further, if you notice, it shows the Department ID in the Dropdown List. Instead of showing
ID, if you want to show the Name of the Department, then please modify the Edit action
method (both Get and Post) of the Employees controller as follows:
// GET: Employees/Edit/5

public async Task<IActionResult> Edit(int? id)

if (id == null || _context.Employees == null)

return NotFound();

var employee = await _context.Employees.FindAsync(id);

if (employee == null)
1127

return NotFound();

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"Name", employee.DepartmentId);

return View(employee);

// POST: Employees/Edit/5

// To protect from overposting attacks, enable the specific properties you want to bind to.

// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.

[HttpPost]

[ValidateAntiForgeryToken]

public async Task<IActionResult> Edit(int id,


[Bind("EmployeeId,Name,Email,Position,DepartmentId")] Employee employee)

if (id != employee.EmployeeId)

return NotFound();

if (ModelState.IsValid)

try

_context.Update(employee);
1128

await _context.SaveChangesAsync();

catch (DbUpdateConcurrencyException)

if (!EmployeeExists(employee.EmployeeId))

return NotFound();

else

throw;

return RedirectToAction(nameof(Index));

ViewData["DepartmentId"] = new SelectList(_context.Departments, "DepartmentId",


"Name", employee.DepartmentId);

return View(employee);

}
Now, it should display the Department Name in the dropdown list. Let us modify the
Employee Department to IT and Position to DBA and click the Save button, as shown in the
image below.
1129

Once you update the data and click on the Save button, it will save the data into the
database and redirect to the Index view, where you can see the updated data, as shown in
the image below.

Now, click the Delete button, as shown in the above image, to Remove the Employee from
the database. Once you click the Delete button, the following Delete View will open.
1130

As you can see in the above image, it is showing the Department ID value instead of the
Department Name. To display the Department Name, modify the Delete view of the
Employees controller as follows:
@model CRUDinCoreMVC.Models.Employee

@{

ViewData["Title"] = "Delete";

<h1>Delete</h1>

<h3>Are you sure you want to delete this?</h3>

<div>

<h4>Employee</h4>

<hr />

<dl class="row">

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Name)


1131

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Name)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Email)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Email)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Position)

</dt>

<dd class = "col-sm-10">

@Html.DisplayFor(model => model.Position)

</dd>

<dt class = "col-sm-2">

@Html.DisplayNameFor(model => model.Department)

</dt>

<dd class = "col-sm-10">

@if(Model.Department != null)

@Html.DisplayFor(model => model.Department.Name)

}
1132

</dd>

</dl>

<form asp-action="Delete">

<input type="hidden" asp-for="EmployeeId" />

<input type="submit" value="Delete" class="btn btn-danger" /> |

<a asp-action="Index">Back to List</a>

</form>

</div>
With the above changes, run the application, go to the Index View, and click the Delete
button. This time, it should display the Department Name instead of the Department ID, as
shown in the image below. Click on the Delete button to delete the Employee from the
database.

Once you click the Delete button, it will delete the employee and then navigate to the Index
view.
While creating and updating an employee, the dropdown list name is displayed as
DepartmentId. If you want to display Department Name instead of DepartmentId, modify the
Employee model as follows. Here, you can see we are decorating
the DepartmentId property with a Display Attribute and setting the Name Property as
Department Name.
using System.ComponentModel.DataAnnotations;
1133

namespace CRUDinCoreMVC.Models

public class Employee

public int EmployeeId { get; set; }

public string Name { get; set; }

public string Email { get; set; }

public string Position { get; set; }

[Display(Name ="Department Name")]

public int DepartmentId { get; set; }

public Department? Department { get; set; }

}
Note: Similarly, you can test the Department Controller and Views and Perform the
database CRUD Operations.
Enhancements
As per your business requirements, you can make the following enhancements:
 Validation: Implement data annotations for model validation.
 Exception Handling: Include proper error handling in your application.
 User Interface: Use CSS and JavaScript to improve the UI.
 Advanced Features: Consider adding features like search, sorting, and
pagination.

You might also like