Instructor-led online courses : ASP.NET Core 2.2, Angular 7, and ASP.NET Core Design Patterns. Read more...

Reverse Engineering EF Core Model and Data Validation Techniques

While working with ASP.NET Core and Entity Framework Core you need to create data model for your application. This calls for creating a DbContext class along with one or more entity classes. Luckily, EF Core allows you to reverse engineer the model from an existing database. To that end this article discusses how that can be accomplished.

Model creation and data validation options

While creating EF Core model and using it in web applications you are looking for two main aspects:

  • How to map entity POCO structure with the database structure
  • How to perform data validations in terms of user input and MVC's model

Beginners often get confused between the available options for performing these tasks. Hence, it's worthwhile to briefly mention them here before we move ahead to reverse engineering features of EF Core.

Two options for creating EF Core model

As far as creating EF model is  concerned you have two options. You can either create DbContext and entity classes manually OR you can use reverse engineering to generate them automatically. This article doesn't discuss the former approach. The latter approach will be discussed in the sections that follow. Obviously, the reverse engineering approach can be used when you have database schema ready with you.

Three options for mapping POCOs to database tables

When it comes to mapping POCOs to database tables you have two options: you can follow certain conventions OR you can use data annotations OR you can use Fluent API. The first approach requires you to create your POCOs such that the class names are same as table names and property names are same as table column names (there are a few more conventions). The second approach uses attribute based syntax to perform mapping with tables and columns. This is a design time way to specify the mapping. The third option uses programmable approach to model configuration and is a runtime way to specify the mapping. Fluent API is often considered an advanced option because it provides certain functionality that is not available with data annotations. 

Two options for performing data validations

Here data validations means MVC level model validation (or view model validation). Again there are two options to accomplish this task. You can either use data annotations OR you can use Fluent Validation library. Fluent Validation is a popular .NET library for building strongly typed validation rules.

If you look at the above options, it would be clear that Fluent API is primarily used for mapping purposes. It doesn't provide data validation capabilities (MVC level model validation). On the other hand data annotations provide mapping as well as data validation capabilities. Although data annotations provide both capabilities, some developers consider this mixing of responsibilities as a poor development practice and prefer Fluent API over data annotations.

Generate EF Core model from existing database using reverse engineering

Ok. Now let's generate EF Core model for tables in the Northwind database. First of all create a new ASP.NET Core web application using MVC template. Then open Visual Studio command prompt and go inside the web application's folder. Then issue the following command:

dotnet ef dbcontext scaffold 
"Server=.;Database=Northwind;Integrated Security=true;" 
Microsoft.EntityFrameworkCore.SqlServer 
-c AppDbContext 
-o Data 
-t Customers 
-t Employees

Here we use EF core scaffolding command to generate DbContext class and entity classes. The command takes a database connection string and data provider name. In this case the connection string points to the Northwind database and uses SqlServer data provider.

The -c option is used to specify the DbContext class name. In this case the DbContext class will be named AppDbContext. The -o option is used to specify the output folder where all the class files will be placed. In this example we store the output in Data folder under project root. If the folder doesn't exist it will be created for you. The -t option is used to specify a table name whose entity class is to be reverse engineered. In this example we ask the tool to reverse engineer Customers and Employees table. If you don't specify -t switch then all the tables are used while scaffolding.

If you issue this command you will get output as shown in the following figure.

Open the AppDbContext class and you will find that the model has been configured using Fluent API.

By default the reverse engineering approach uses Fluent API to map the entity classes with the database tables. If you would like to use data annotations instead of Fluent API you can modify the command as follows:

dotnet ef dbcontext scaffold 
"Server=.;Database=Northwind;Integrated Security=true;" 
Microsoft.EntityFrameworkCore.SqlServer 
-c AppDbContext 
-o Data 
-t Customers 
-t Employees
--data-annotations

The --data-annotations switch indicates that data annotations are to be used instead of Fluent API. This time you will find that Customers contains several data annotations shown below:

Testing the behavior - Fluent API vs. Data Annotations

From our earlier discussion you know that Fluent API mainly provide mapping capabilities whereas data annotations provide mapping as well as data validation capabilities. You can quickly confirm this by creating a view like this:

The view simply displays Customer properties for editing using the EditorForModel() helper. The ValidationSummary() helper displays validation error messages if any.

The Index() actions responsible for supplying data to this view are shown below:

public IActionResult Index()
{
    using (AppDbContext db = new AppDbContext())
    {
        Customer obj = db.Customers.Find("ALFKI");
        return View(obj);
    }
}

[HttpPost]
public IActionResult Index(Customer obj)
{
    using (AppDbContext db = new AppDbContext())
    {
        if (ModelState.IsValid)
        {
            db.Update(obj);
            db.SaveChanges();
            ViewBag.Message = "Model contains valid data!";
        }
        else
        {
            ViewBag.Message = "Model contains invalid data!";
        }
        return View(obj);
    }
}

I won't go into the details of these actions since they are quite straightforward.

Now, reverse engineer model using Fluent API and run the application. You will find that ModelState.IsValid returns true even if you enter invalid data (say you enter CustomerID greater than 5 characters). An exception is thrown at SaveChanges() line when an attempt is made to save the data (the error might vary depending on the kind of operation you are performing).

 

Now, reverse engineer model using data annotations and run the application. This time validation works as expected and ModelState.IsValid returns false for invalid data.

Using Fluent Validation with Fluent API

By now it should be clear to you that reverse engineering with Fluent API needs some data validation strategy. Since you are using Fluent API for mapping why not use Fluent Validation for data validations? That's what we are going to do.

To use Fluent Validation you need to install the Fluent Validation library using Manage NuGet Packages dialog.

Make sure to install FluentValidation.AspNetCore package as shown above.

Then add a new class called CustomerValidator in the Data folder (or whatever folder you store your reverse engineered files). The CustomerValidator class implements the validation rules and is shown below:

public class CustomerValidator : 
AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor(x => x.CustomerId).NotNull().Length(5);
        RuleFor(x => x.CompanyName).
NotEmpty().Length(3, 50);
        RuleFor(x => x.ContactName).
NotEmpty().MaximumLength(50);
        RuleFor(x => x.Country).MaximumLength(50);
    }
}

Notice that the CustomerValidator inherits from AbstractValidator<Customer> base class. We configured the validation rules such as CustomerId property doesn't contain a null value and its value is exactly 5 characters. The CompanyName property is not empty and and 5 to 50 characters. The ContactName is configured to be not empty and maximum length of 50 characters. Finally, Country is configured to have maximum of 50 characters. You may read the official documentation of Fluent Validation to know more about configuring validation rules.

Now open the Startup class and add the following lines in the ConfigureServices()

services.AddMvc().AddFluentValidation();
services.AddTransient<IValidator<Customer>, 
CustomerValidator>();

The above code registers FluentValidation with the DI framework. Then it registers a transient instance of CustomerValidator with the DI container. This way our web application is now aware of Fluent Validation.

Run the application and try entering some invalid data. This time ModelState.IsValid will return false because Fluent Validation library is doing the validations for us.

That's it for now! Keep coding!!


Bipin Joshi is a software consultant, trainer, author, yoga mentor, and spiritual guide having 24+ years of experience in software development, consulting, and training. He conducts instructor-led online training courses in ASP.NET Core, ASP.NET MVC, and Design Patterns for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get article updates : Facebook  Twitter  LinkedIn

Posted On : 25 February 2019


Tags : ASP.NET ASP.NET Core Data Access MVC .NET Framework C#


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates