ENTITY FRAMEWORK CORE

MAPPING CLASSES OF ENTITIES

NET COREThe Database First approach is not fully supported by Entity Framework and this is because the Tool we used is destructive. Suppose we want to add a column to one of our tables, however, we want the same column to be found in the corresponding entity class, if we run the previously used Tool again, all entity classes would be overwritten causing us to lose any changes we made. So CAUTION if we create a new column in the database we must be the ones to manually map the corresponding property in the entity class.

CODE FIRST APPROACH

The alternative is the Code First approach, first we create the entity classes and their relationships and let Entity Framework Core create the database using the Migrations mechanism. The issue of Migrations we will see later for the moment let us do the mapping between the conceptual and relational worlds. As long as the property has the same name as the database column, we can avoid mapping. The entity class is part of the application code and therefore should also contain application logic unlike ViewModels.

Classi di entità

The image shows the code of the Course entity class.

Course

Let’s look at mapping with the flowing interface of Entity Framework Core.

Mapping

MAPPING RELATIONSHIPS AND COMPLEX TYPES

Let us see how the Money class is mapped and the relationships between Courses and Lessons.

Owned Types

The monetary information needs to be close, so we encapsulated it in a Money class that has two properties, the currency which is an Enum and the amount. See mapping.

Mapping owned types

Now let’s go to see how relationships are mapped.

Relazioni

HasMany(…) indicates that a course can have many classes. With WithOne(…) we take us to the lesson side. For one class there is only one course. The last property indicates the foreign key, which in our case will be CourseId.

Chiave esterna

EXTRACT THE LIST OF COURSES FROM AN APPLICATION SERVICE

The MyCourseDbContext we created is part of the infrastructure service. Let’s see how to use it from an application service class that we will call EfCoreCourseService, which I remind you must implement the ICourseService interface.

Servizi

Let’s look at the method of course extraction. It’s an asynchronous method because I remind you that Entity Framework Core has to connect to the database and send SQL queries the moment we make a request for data from the database.

Estrazione dei corsi

EXTRACT RELATED ENTITIES OF THE COURSE

Let’s look at the code. The AsNoTracking() statement is an optimization of the query. To extract the classes we must use the extension Method Include to which a lambda must be supplied.

Dettaglio e lezioni del corso

UNDERSTAND IQUERYABLE<T> AND DEFERRED EXECUTION

We have seen that Entity Framework Core allows us to work on a conceptual model, built on a set of objects called entities and queryable with Linq queries. We must never make the mistake of indulging in all this convenience while forgetting that there is a relational database underneath anyway. Returning to the course extraction method, the SQL query is sent to the database when we manifest the intention to read the data, this is for efficiency reasons.

Deferred execution
IQuerable

USE TECHNIQUES TO IMPROVE PERFORMANCE

Let’s look at the AsNoTracking() instruction.

AsNoTracking

Let’s look at the other expedient. We have already talked about Connection Pool and with Entity Framework Core the operation is similar.

ContextPool

SECTION SUMMARY

USING AN ORM

An ORM (Object Relational Mapper) is a technology that provides access to data located in relational databases. Data can be retrieved with strongly typed queries, rather than SQL queries that are prone to make us make typing errors.

The ORM is responsible for “translating” our strongly typed queries into SQL. It also reads the results found from the database and returns them to us in the form of objects (or object graphs) thanks to a step called materialization. An ORM is thus an abstraction layer that tends to obviate the rigidities of the relational world and allows us to take advantage of the full expressiveness of a language such as C#. On the other hand, an ORM has a performance cost precisely because it must perform some mapping logic to “adapt” the peculiarities of the relational world to those of the object-oriented world.

The ORM we evaluated is called Entity Framework Core and was built by Microsoft on top of ADO.NET, which it uses at a low level to interact with the database. It allows us both to submit strongly typed queries with LINQ and also SQL queries when needed.

LAMBDA EXPRESSION

Writing a lambda expression is a very concise way of defining an anonymous function that accepts parameters and returns a value just any other C# method. Here is an example:

  1. //Define a lambda expression that receives two string parameters and returns a string value
  2. //The lambda operator, i.e., the symbol =>, separates parameters from the body
  3. (string firstName, string lastName) => $”{firstName} {lastName}”;

This writing is equivalent to the definition of the following method (except for the name, which is absent in the lambda).

  1. private string FirstName(string firstName, string lastName)
  2. {
  3. return $”{firstName} {lastName}”;
  4. }

As the name implies, a lambda expression is an expression and therefore can be assigned to a variable or passed as an argument in invoking another method. Here is an example:

  1. //Assign the lambda to a variable named getFullName
  2. //Here the parameter types are not needed: the compiler infers them from the type of the variable
  3. Func<string, string> getFullName = (firstName, lastName) => $”{firstName} {lastName}”;
  4. //And then I pass it as an argument in invoking a method called SortPeople
  5. SortPeople(getFullName);

What is the purpose of passing a lambda expression as an argument? Well, this way we can provide our own custom logic to another method that will do the “heavy” work instead.

For example, if the method deals with sorting a list, we with the lambda expression simply tell it the criterion according to which we want the list to be sorted (e.g.: by first name or first by first name and then by last name, etc…).

Lambda expressions can be assigned to variables of various types:

  • Func<T1, T2, …, TResult> The Func type is used when the lambda expression must return a value. The last type argument (TResult) indicates what type the returned value should be. All the preceding type arguments (T1, T2, …) indicate the number and type of arguments that the lambda expression must accept;
  • Action<T1, T2, …> The Action type is used when the lambda expression should return nothing (equivalent to a void method). The type arguments (T1, T2, …) indicate the number and type of parameters the lambda should accept.

COMPOSE LINQ QUERY

LINQ is a technology used to query lists of elements, that is, objects that implement the IEnumerable interface. To “query” means to filter, sort, project, and aggregate the list according to our logic. For this purpose it provides a multitude of extension methods, defined in the System.Linq namespace, which we use to compose LINQ queries.

For example, the extension method Where deals with filtering a list of elements i.e., producing a new list containing a variable number of elements, from 0 to all, based on our logic that we pass to it with a lambda expression. Here is an example:

  1. //Given an array of 6 integers
  2. var numbers = new int[] { 1, 2, 3, 4, 5, 6 };
  3. //We want to get the list of even numbers only.
  4. //The lambda we provide is called for each of the numbers in the list
  5. //E will return true if the remainder of the division by 2 is 0
  6. var evenNumbers = numbers.Where(number => number % 2 == 0);
  7. //E we print them out with a foreach
  8. foreach (var evenNumber in evenNumbers)
  9. {
  10. WriteLine(evenNumber);
  11. }

LINQ has numerous extension methods that we will see over time. Here are just a few:

  • Where produces a new list containing only those items that conform to our filter criterion;
  • Select projects the elements, that is, it produces a new list whose elements are of another type;
  • Sum, Count and Average produce a scalar value by summing, counting or averaging the items in the list.

START WITH ENTITY FRAMEWORK CORE

Entity Framework Core, just like ADO.NET, is able to work with various database technologies thanks to providers made by various vendors. To begin, we must then install the appropriate NuGet package, which will get us the provider for Sqlite. In our case, we execute the command:

  1. dotnet add package Microsoft.EntityFrameworkCore.Sqlite

The complete list of supported database technologies and their providers can be found at this address:
https://docs.microsoft.com/it-it/ef/core/providers/

CREATE THE CONCEPTUAL MODEL

We can follow two approaches:

  • If we follow the database-first approach it means that we have already created our database and therefore we can automatically generate the conceptual model classes. Microsoft has made available the dotnet ef dbcontext scaffold command that can reverse engineer the database, that is, examine existing tables and, for each, generate the respective class;
  • If instead we follow the code-first approach then we have to write the conceptual model classes ourselves by hand. Next we will have Entity Framework Core generate the database schema thanks to migration, which we will see later.

It is important to note that the database-first approach is not properly supported by Entity Framework Core. In fact, the reverse engineering tool cannot detect the changes we make to the database structure from time to time and therefore cannot update the classes in the conceptual model.

In addition to the conceptual model classes, in our project there will be a class derived from DbContext in which we will insert the mapping between object model and relational model.

MAPPING THE CONCEPTUAL MODEL TO THE RELATIONAL MODEL

Entity Framework Core acts as a bridge between the object model and the relational model. In order for it to be able to fulfill this responsibility, we must instruct it on how each class in the conceptual model remaps to its respective table(s) in the database. We indicate the mapping in the OnModelCreating method of the DbContext.

Entity Framework Core has a fluent interface that allows us to map:

  • Entity classes: these are the classes such as Course and Lesson, which have their own identity (i.e., a primary key);
  • Owned types: are classes such as Money, lacking their own identity and created for the sole purpose of holding certain values (such as currency and the value of a monetary amount) together.
  • Relationships: these are the constraints that bind two classes of entities together. For example, between Course and Lesson there is a one-to-many relationship (multiple lessons exist for each course).

Thanks to mapping, we can model classes as we like and thus take advantage of the full potential of the C# language (inheritance, complex properties, etc…). In this way we are no longer forced to follow a rigid data structure like that of the relational world.

Also, in the OnConfiguring method of the DbContext we obviously need to indicate which database technology we want to use and what the connection string is. To finish, we create properties in the DbContext that expose the DbSets, that is, the objects to which we will address our LINQ queries

REGISTER DBCONTEXT FOR DEPENDENCY INJECTION

Entity Framework Core is well integrated with the dependency injection mechanism of ASP.NET Core. Our application services can then have a dependency on the DbContext, as long as it has been registered by the ConfigureServices method of the Startup.

Alternatively, we can use the AddDbContextPool method, which will allow for better performance because instances of our DbContext will be created proactively, similar to the way ADO.NET’s connection pool works.

It is important to note that the SQL query is sent to the database as late as possible, that is, at the line of code where we are going to read the results. This peculiarity is called deferred execution.

Other situations in which Entity Framework Core sends the SQL query are:

  • When we cycle the LINQ query with a foreach;
  • When we use other aggregation extension methods such as Count, Average, Sum (and their asynchronous variants).

To optimize performance, remember:

  • Use the extension method AsNoTracking if we only need to read the results. So we avoid Entity Framework Core tracking changes to entities, which would be unnecessary in this case;
  • Do the mapping between entity and viewmodel as a single expression within the Select extension method (as in the example), otherwise too many fields may be involved in the SQL query, even those we don’t need.

LINK TO CODE ON GITHUB

GITHUB

Download the section11 code or clone the GITHUB repository to have all sections available in your favorite editor.