© Brian L. Gorman 2020
B. L. GormanPractical Entity Frameworkhttps://doi.org/10.1007/978-1-4842-6044-9_3

3. Entity Framework: Code First

Brian L. Gorman1 
(1)
Jesup, IA, USA
 

In this chapter, we are going to look at what it takes to get up and running with Entity Framework using the code-first approach. As we move through this chapter, we’ll learn about the code-first approach to Entity Framework, and we’ll take note of some of the advantages that working with a code-first approach brings to our development process. We’ll conclude this chapter by working through some activities to create a couple of code-first Entity Framework projects in EFCore and EF6.

Code first doesn’t always mean code first

Even though the name code first implies that the database doesn’t exist until code is written, it is entirely possible to employ a code-first approach with an existing database, as well as in a new greenfield project. As with any development scenario, there are multiple things to consider when attempting to determine if the code-first approach is correct for your project.

When not to use the code-first approach

Sometimes when trying to determine when something is the right answer, the way to start is to determine when it is not the right answer. That being said, in most production applications that you’ll encounter in today’s world, there are very few reasons that code first doesn’t make a lot of sense.

The primary reason to avoid using the code-first approach would relate to having a legacy system that is not capable of supporting the required tools, for example, a project that was written in any .Net Framework prior to .Net 3.5. In those projects, Entity Framework didn’t exist, so using the code-first approach is simply not possible.

Another reason that you may be forced to avoid the code-first approach could be organizational restrictions. Perhaps there are greater security concerns at play, making it against the law or highly dangerous for your company to expose so much power over the data structure to any developer through code. Perhaps your company will not allow anyone but that one mysterious DBA to touch the database for any reason. In both of the previous cases, there may be some training or education that can overcome the issues, or it may truly just be impossible to work with code first in your development efforts.

Yet another reason to avoid using the code-first approach with EF could be due to personal preference. Perhaps you don’t like the normalization structure required to use an ORM. You might also have another solution that you prefer for database interaction, such as F# type providers. Maybe you’ve been using NHibernate and you don’t want to change something that you know already works, although you could also do code first with NHibernate.

A final reason to avoid using code first could simply be that there is a high risk of losing data in a mature database. While it is entirely possible to overcome this issue, there will always be a chance that forgetting to plan for database migrations that affect data can (and perhaps will) happen. Before leaning on this argument as the reason not to choose code first in your solution, please remember that you would have to overcome the same data loss issues in traditional database development and that the solution is usually exactly the same. For example, it is entirely possible to create a migration that runs a script that backs up data from a table and then run a migration to modify the table, truncating or causing data loss, and then another final migration to restore the original data massaged to fit the new table structure, just as you would traditionally have to do with scripts to modify the database structure. As we’ll soon see, the advantages of a code-first approach could even make it a better choice in this situation.

When to use the code-first approach

If you’re wondering when you should use the code-first approach going forward, simply put, the answer is likely going to be every time you can. While there may still be situations as discussed in the previous section that exist where you cannot use the code-first approach, anytime you can use code first, you should use code first.

With EFCore and EFvNext, there is no longer an ability to create a model file like the EDMX file we saw in the previous chapter. While we can always generate a reverse-engineered database, the fact remains that we will likely have a model-based approach to all development going forward. This is a very good thing for several reasons.

Code first in an existing project

Now that you’ve bought in and are ready to build out a code-first approach with Entity Framework, what do you do when you have an existing and mature project, with an existing database? Since the database already exists, we could begin by scaffolding the data models such as we’ve seen in the reverse-engineering approach to get the models auto-generated in our solution, as well as have the DBContext generated and populated for us (as we’ve seen in Chapter 2). To make the project operate in a code-first manner, we would then just need to enable migrations and start working with the migrations against the data structures.

From then on, the project would be able to continue to build out new models and database objects and apply further migrations as needed. A great level of care would still be needed in this approach, however, as the database that is already mature needs to be protected from accidental changes that might truncate data from tables or break critical performance enhancements (such as a change dropping a view or an index might do), especially if other line-of-business applications are relying on these data structures for normal operation.

Code first in a new project against a mature database

Another approach that might be taken when working with Entity Framework in a code-first manner might be to develop a new application, but still need to use an existing database.

In a situation such as this, the development team will need to again use a great deal of caution to avoid breaking legacy functionality that might exist in other applications. Additionally, any changes made in the code-first project would need to be propagated into any legacy applications to avoid potentially causing outages or even more disastrous consequences for other business units in the organization.

Code first in a new project with a new database

This greenfield scenario is an obvious choice for working with a code-first approach. Even when we don’t want to use migrations, at some point we still need data models that define how to work with the various database objects in order to use Entity Framework.

Since the project is new and has a new database to accompany it, using code first will provide the best flexibility and ease of use from our codebase. In this case, it only makes sense to use the code-first approach.

The benefits of a well-executed code-first development effort

In case there is still any doubt about the level of success your team can achieve by using the code-first approach, I’d like to take a moment to highlight some of the greatest benefits of using the code-first approach.

Ability to get up and running quickly

Since the entire database structure is defined in code via migrations, any developer can open the project, validate the connection string works, and run a simple command to get the database in the exact state that it is in any environment where it is deployed. Obviously, there would still be work with some data, as while some data would likely be created by seeding the database, there would be a number of data tables that need human interaction. This is no different than the issues any project using a database would encounter.

A complete record of database changes in source control

As mentioned previously, using the code-first approach allows for every piece of the database to be imperatively defined in code. As the structure and needs of the database were changed, these changes were implemented in code files and a new migration was created and executed to affect the changes on the database.

In the past, you might have tried migrations and found them to be tricky. In fact, migrations before EFCore might have even caused you pain when multiple developers created conflicting migrations. Even if the migrations didn’t conflict, you still were forced to re-scaffold your migration if another developer pushed theirs first since the overall model hash code saved in the database would be different. With EFCore, most of these pain points have been eliminated, and, although migration conflicts can still happen, they are mainly the result of conflicting changes to the same database objects.

Since our code is defined directly within the project in this approach, the files and changes are all tracked in source control. There is no longer any need to create a database project with a bunch of generated and non-generated scripts, or worse, manually put your scripts into source control and hope developers keep them up to date.

Having the changes in source control is a very important advantage and should not be taken lightly. If drives and backups fail, there is always a potential of losing your database entirely. Even if you don’t lose your database, when a database failure happens, you would likely still lose all the transactions that had been run since the previous backup. Although both database and backup failure combined is rare and may never happen, if it did, and you still have your project code, migrations could be run to restore the structure and seed data from the database. Really this feature is more useful for developer machines. As soon as one developer’s changes make it into your developer source branch, other developers can update their own local database with a few quick commands. This is highly advantageous when it comes to avoiding conflicts and bugs.

Agility when needing to revert to a previous state

With the code being in source control, EF migrations also have the added benefit that, when written correctly, can easily roll back a change against the database. Rolling back a change can be a destructive event that loses data, but this is also something that is rarely, if ever, done in production. In fact, there is a camp where some users don’t rely on rollbacks at all. The theory there being that just adding another migration to move the database back in a forward direction is a better approach. Either way, you’re still going to need to plan for how the data is affected.

With the ability to revert, however, it is extremely easy to set a local developer database to match the exact state a database was in at any point of development. For example, it is easy to roll back the database to the state in time when a bug was introduced to your codebase or a patch was released. This allows for effectively coding against the database as it was at that time, making it easier and safer to release a common modification across all official releases of your project or to patch a bug fix.

Another advantage of the migrations is the fact that changes can be reverted at will. For example, if a feature is released and then eventually eliminated, migrations allow the feature to continue to exist at a patch level but to be removed from future development.

Having this history and ability to easily reset the database to the state it needs to be is all managed by the code in the migrations. Therefore, as a developer, you don’t have to spend your time trying to remember which scripts to run and testing to make sure your tables and other object structures are correct for the patch, fix, or feature on which you are working.

Shifting from declarative to imperative database programming

Another important concept with the use of code-first database development is that we are making a conscious transition to imperative database programming and saying goodbye to declarative programming around our database.

Imperative programming is the concept that as a developer, we are directly defining what should happen, thereby locking in the details of the implementation, leaving little to interpretation or fluctuation of implementation.

Declarative programming is just getting to an end result, regardless of how you get there. In this paradigm, often the details of the implementation can be murky or fluent, as long as the result is achieved.

For example, a declarative approach to development around the database might look something like you know there is a table that holds some data that was defined somewhere. You could query that data and perhaps connect to another table or maybe a view to build out a result set, but as long as the data shows up, it is not important how you got it to render. Also, you can sort of count on some fields being in the table for the important information like name, age, date of birth, email, or maybe even a phone number, but it may have changed, so you better double-check before counting on that data. If the data isn’t there, or has changed, maybe I can ask to store that important information somewhere and someone can build out the database scripts so that I can get it eventually.

An imperative approach is more defined, and code first is most definitely imperative by nature. Every database structure is exactly modeled in code. This means you know exactly what tables exist and what fields exist on those tables. In fact, you can easily create an instance of a model that holds exactly the correct data, with exactly the correct limitations that exist in the database, including type and any other constraints like length or range. Furthermore, relationships are directly defined, so you can be certain that a foreign key exists in each related table and you can easily query and populate related data.

For the most part, Entity Framework has always been somewhat imperative, with well-defined structures in place. However, the code-first approach has solidified the imperative approach with the ability to force the database to conform to specific requirements, rather than relying on things to potentially be implemented correctly in the database.

It’s time to see code-first database programming in action

Now that we’ve seen some of the advantages and reasons behind using a code-first approach, it’s time to dive in with a couple of activities. These activities will help us learn more about how the code-first approach works and also see the power that it gives us to work with this approach.

One thing we will not see here is what it would take to put code first into an existing project that is mature. The overall approach would be the same as if using against an existing database. Code would then need to be updated to start working against the EF library for new and maintenance development, and the original connections and code (such as ADO.Net implementation) could remain in place.

In the next three activities, we’ll look at using code-first approach in a new greenfield project in both EFCore and EF6 . We’ll also use EFCore to create a new implementation against a mature database.

I want to take a final moment before diving into some coding activities to make sure a couple of other things are clear. We’re about to learn how to implement the Entity Framework against an existing and a new database in EFCore and also an existing EF6 project (this would be a scenario such as upgrading an older application to use EF6 in the .Net Framework).

Please note that in order to keep the focus on the actual implementation and use of Entity Framework, I’ve chosen to make the startup projects work as console applications. We all know this is not likely to be how your project will work in the real world. However, learning to do things like making web controllers and displaying data on views or rendering information to Xamarin forms, or other similar practical activities, is outside of the scope of this book. It is my belief that if you are a web developer or a Xamarin developer or a UWP or WPF developer, you already have the skills you need in those arenas (or you will likely have resources available to learn them). Therefore, the choice to restrict the GUI portion of these activities to a minimal implementation is a conscious choice.

With that choice comes a small price, however, which I feel is important to address. If you are building out solutions in WPF, UWP, Xamarin, and/or ASP.Net MVC, it is highly likely that those project templates scaffold out an implementation directly to Entity Framework for you, so going through the setup and working in a new manner may not be necessary in many of these cases. Even so, learning how to build out a solution from the ground up will position you to rearchitect your solutions to make a more robust implementation. By the end of these activities, you’ll likely have everything you need to understand how to build out an Entity Framework code-first solution into any existing or new project.

Activity 0301: Creating a new code-first implementation against an existing database project in EFCore

In this first activity, we’re going to go through building out an EFCore code-first implementation against an existing database. This will give us the opportunity to see what it might be like to spin up a new project in a mature business environment, against a mature database that likely has other line-of-business applications working against it.

Use the starter files, or your project from Chapter 2

To begin, we’re going to pick up where we left off at the end of Chapter 2, where we had built out a reverse-engineered database project against the AdventureWorks database, using Entity Framework Core.

If for some reason you do not have these files or you simply want a fresh start, the code resources for this book include a starter zip file package for this activity called Activity0301_EFCore_Starter .

I did modify the implementation a bit to use a singleton configuration builder and of course named my project for this chapter; other than that, everything else is the same as where we landed at the end of Chapter 2. At any point, you can use the starter files or leverage the finished files Activity0301_EFCore_Final as a reference during this activity. At this point, I’m assuming you are well versed in getting started with Visual Studio and getting a project open or up and running, so we’re going to dive right in. Moving the builder code to a singleton is not necessary; I’ve simply done this to get the code out of the way of our learning. If you want to see how to implement, you can review the implementation in the starter files for activity 0301.

Step 1: Setup and getting started

To begin, either open the starter files entitled Activity0301_EFCore_Starter.zip or your previous files from Chapter 2. Once the project is opened, validate that you have the correct database connection in the appsettings.json file(s). Once that is correct, validate that your project structure is similar to what is shown in Figure 3-1.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig1_HTML.jpg
Figure 3-1

The initial project structure is shown

Once you’ve validated the project setup, run the project to verify that it is working correctly as per the EFCore activity from Chapter 2.

If we run the project as is, we get a console output of the list of the last 20 non-distinct users in the person table as shown in Figure 3-2.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig2_HTML.jpg
Figure 3-2

Simple console output at the start of the project

This is essentially where we had left off using the existing database at the end of Chapter 2.

Step 2: Make sure EF is ready to scaffold migrations

Ensure that you have installed the Microsoft.EntityFrameworkCore.Design package on the starter project. To do this, right-click the Solution and select Manage NuGet Packages for Solution. Once the window opens, select the Installed tab and then make sure that Microsoft.EntityFrameworkCore.Design is installed with the latest version of EFCore.

If the design package is not installed, switch to browse and then find the package and install it to the starter project. The EF_Activity001 project will not need this library. You can see what this looks like as shown in Figure 3-3.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig3_HTML.jpg
Figure 3-3

Use the Manage NuGet Packages for Solution dialog to ensure that the Microsoft.EntityFrameworkCore.Design package is installed

Step 3: Create the initial migration

Now let’s create our initial migration in order to begin working with the code-first approach in our application.

To begin, let’s try running a command in the PMC. The command to run is add-migration “Initial Migration.” Before running the command, make sure that you have selected the Entity Framework library project that contains the actual database context. If you fail to select the correct project, you will get an error. Figure 3-4 shows the incorrect project selected.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig4_HTML.jpg
Figure 3-4

Running a command with an incorrect default project selected

Running this command generates the following error as shown in Figure 3-5.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig5_HTML.jpg
Figure 3-5

The error received when trying to run migrations against an incorrect default project

../images/491632_1_En_3_Chapter/491632_1_En_3_Fig6_HTML.jpg
Figure 3-6

Failure to create database migration when multiple contexts are present

Change the Default project drop-down to point to the project with the actual AdventureWorks DBContext in it, and then run the command again. Do you think it will work? Figure 3-6 shows the outcome.

In this case, it didn’t work, because we have two database contexts in our project. If you remember back to Chapter 1, we created a context by default for our use. Then in Chapter 2 we generated a new context. Up to this point, we haven’t used migrations, so we directly instantiated the correct context when we needed it. Now, with two contexts, Visual Studio doesn’t know which one to use to scaffold the migration. Later in the book, we’ll cover how to work with multiple contexts. For now, let’s just delete the unused context from this project. Find the file ApplicationDbContext.cs that we created in the EF_Activity001 project, right-click it, and select Delete. This is shown in more detail in Figure 3-7.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig7_HTML.jpg
Figure 3-7

Delete the unused database context file

When the warning pops up about permanent deletion, just select OK and allow the file to be destroyed.

It is important to remember that changes to the structure of the project require a rebuild before attempting to add the migration.  After deleting the second database context, remember to rebuild the solution before moving on.

If we fail to rebuild and try to apply the migration, we will get the exact same error as before, letting us know that more than one DbContext was found.

With the file now deleted and the project rebuilt, once again, let’s attempt to make our first migration. Do you think it will work this time? Review Figure 3-8 to see what happens when we run the command again.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig8_HTML.jpg
Figure 3-8

The initial migration has been created

Step 4: Review the migration

Now that we have a migration ready to go, we might be tempted to jump right in and run the migration. This would be a very big mistake, as the initial migration scaffolded has a lot of tables that it is planning to create which already exist in our database.

Remember our earlier discussion of the pros and cons of using the code-first approach? This is one of the cons. Right now, we have no migrations applied in the database, so the migration builder thinks we need to create all the tables. However, our database already has all the tables, so we need to make sure that they don’t get created for us. Review Figure 3-9 to see the table creation statements as generated in the migration.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig9_HTML.jpg
Figure 3-9

The initial migration contains all the table create statements, even though tables already exist in the database

So, what should we do next? There are actually a couple of approaches we can take. One approach would be to comment out everything in the “Up” method and apply the migration with the update-database command.

This approach should work, but it begs the question about what the next developer would do, and the developer after that, if they have a fresh start on an existing database on their machine. A potential solution to that problem could be to run the migration with the code commented out and then propagate the changes and run on other machines or simply modify other developer databases so that the first migration would appear to have already been applied. However, no matter what we do here, care would need to be exercised when running for the first time in production to avoid any potential problems.

Since our database is existing, there should not be a reason that we need to generate the tables as is. Therefore, let’s go ahead and comment out the code in the Up method. You could also remove it if you wanted, assuming everyone would have access to an existing sample of the database. Additionally, we will likely never want to delete these existing tables, so let’s also entirely delete the code in the Down method.

Step 5: Comment out the code in the “Up” method and delete Down method code

Comment out the code in the Up method, and delete the code in the Down method in this initial migration. Since the Up method is very large, collapse the method and then select it; then just hit the key chord ctrl+k and then ctrl+c to apply the block comment. Once that is complete, uncomment the method declaration and closing brace. Figure 3-10 shows how we can comment the code in the migration to avoid execution when the update-database command is run.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig10_HTML.jpg
Figure 3-10

The Up method code is commented out and the Down method code is revmoved for the initial migration

With the migration prepared to run with no effect on our data, let’s feel free to run the update-database command in the PMC and see what happens.

There is no need to fear this command right now. If this operation goes sideways, we can restore from backup.  Please do not do this against a production database until you are certain the results you want will be achieved.

In the PMC, run the command update-database as shown in Figure 3-11.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig11_HTML.jpg
Figure 3-11

Running the update-database command in the PMC

Step 6: Examine the database

Looking directly at the database, we can now see that a table was added, and we have an entry in it. Notice the command I ran earlier performed an insert into the database table __EFMigrationsHistory. Let’s look at this table in our database. Figure 3-12 shows the database structure after the migration is executed.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig12_HTML.jpg
Figure 3-12

The EF Migrations History Table with the initial migration tracked as having been run. Note that the table can be viewed from SSMS or from within Visual Studio using the Server Explorer

Now that we have the data reviewed, we can see how EF knows what migrations to apply in our database. If the migration exists by name in the table (MigrationId column), then the update-database command will not run that migration.

Having the name of a migration in the table prevents execution.  For other developers and production databases, we could simply script out and then add the __EFMigrations table to developer or production databases and then insert the first entry by Id so that the initial migration will never be executed on another database.

Here we see the name of the database migration as generated, which is nothing more than a datetime stamp with the name of the migration as named by us. It would be very easy to script the __EFMigrations table, insert it into any database that is going to work with this project, and insert the first record into the table to prevent the migration from ever being run.

Step 7: Add another migration to see what happens

What happens if we leave the code commented out? Let’s leave the migration as is and then add another migration to find out. Run the command add-migration “testing migrations” to see what is generated. Figure 3-13 shows the expected output when no changes exist, which is what we should see now.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig13_HTML.jpg
Figure 3-13

Adding a second migration

As we can see, this migration is blank. Therefore, we do not need to uncomment our code (in fact, we could remove it) from the first initial migration. With that in mind, we should be ok to push to another machine with the existing database or even our production machine without fear of causing any problems in the future. That being said, it’s always wise to make a quick backup before doing something like this, just in case. If you can’t get a backup due to regulations, space, or other mitigating factors, then you could consider scheduling your initial deployment to run immediately following your next automated backup.

Step 8: Remove the blank migration

In the last step, we added a migration that has nothing in it. Therefore, we should just remove it from our migrations as it is not accomplishing anything. If you noticed, the last statement in the PMC when we ran update-database was “To undo this action, use Remove-Migration.” Let’s go ahead and run that remove-migration command now. The command is shown in Figure 3-14.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig14_HTML.jpg
Figure 3-14

Removing a migration

In this case, we had not applied the migration. If for some reason you had run the migration that was blank, you would first need to roll back your migration history to the previous migration and then run the remove-migration command. Although we are not covering this in our activity, the command to roll back a migration would be as follows: update-database -migration [name-of-your-migration] command; in this case, it is something like update-database -migration InitialMigration. Please also note the command is different in EF6, where we would specify -target instead of -migration in the command to roll back a migration [update-database -target [name-of-your-migration].

Final thoughts

In this activity, we saw what it takes to get our database set up to work with code-first migrations when the database already exists. We did not cover how to start modifying the database, but we are position to do so.

The most important takeaways from this activity are as follows:
  • It is very easy in EFCore to get up and running with code-first against an existing database.

  • Make sure to avoid using destructive code in your initial migration. The system should be smart enough from that time on to not try to re-create the database.

  • Use the commands add-migration [migration name] and update-database to create and execute migrations.

  • Use the command remove-migration to remove a migration that has not been applied to the database. If the migration is applied, use the update-database command with the -migration [migration name] flag to first roll back to the previous migration and then run the remove-migration command.

In the next activity, we’ll start fresh with a new project and a new database, and then we’ll see what it takes to start modifying data in our new database using code-first migrations. Do not fear, the ability to modify data as shown in the next activity would work in exactly the same manner in our existing database project from this point on.

Activity 0302: Creating a new code-first project in EFCore

In this second activity, we’re going to create a new code-first project in EFCore . To begin this activity, we’re going to start a new project, with a new purpose and setup. We’ll set our connection strings as before within the configuration files, and then we’ll start working with the code-first approach with a new database. Although you likely have similar code in place, it may be confusing where I’m starting with this activity. For this reason, I recommend that you simply start with the files from the project Activity0302_EFCoreNewDb_Starter which has been pre-configured with a code library and startup console project. Feel free to update the versions of EFCore as to the latest version at the time you are starting this project.

What are we building?

In this activity, we’re going to build a simple database to manage inventory. Inventory items could be any object you have around your house, such as a bunch of movies or books or board games, and can also include items like computers, cameras, and even clothes. We will be building this from the ground up, and this will be the start of what we’ll be building with for the remainder of the book.

If you are using the starter files, skip to step 2.  Step 1 is going to show how to build this project from the ground up.

Step 1: Set up and use a new project

To begin from scratch, create a new .Net Core Console project and name the project Activity0302_EFCoreNewDb. Start by opening Visual Studio and selecting Create a new project. Creating a new project is shown in Figure 3-15.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig15_HTML.jpg
Figure 3-15

Create a new project

Create a new Console App (.Net Core) project. Selecting a console app is shown in Figure 3-16.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig16_HTML.jpg
Figure 3-16

Creating a new .Net Core Console project

Configure your project to save to a location on your drive that is easy to find; name the project as stated above Activity0302_EFCoreNewDb. Figure 3-17 shows how we can name the project and select the storage location for our code.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig17_HTML.jpg
Figure 3-17

Configuring the new project

The project should be created as expected. Once created, it should look similar to what is shown in Figure 3-18.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig18_HTML.jpg
Figure 3-18

The current project as generated during creation

Next, we need to add a project to house our database operations. Right-click the Solution and select AddNew Project (see Figure 3-19).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig19_HTML.jpg
Figure 3-19

Adding a new project to the solution

For this project, select Class Library (.Net Core), shown in Figure 3-20.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig20_HTML.jpg
Figure 3-20

Use the .Net Core Class Library template

Name the new project InventoryDatabaseCore and set it to save in the same folder as your other project. Review Figure 3-21 for more information on configuring the project.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig21_HTML.jpg
Figure 3-21

Configure the DB Project name and folder location

Add a reference to the new project in the Activity0302_EFCoreNewDb project by right-clicking the project and selecting Add ➤ Reference (as shown in Figure 3-22).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig22_HTML.jpg
Figure 3-22

Adding a reference to the new DB project in the starter project

Select the DB project to reference it in the starter project (see Figure 3-23).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig23_HTML.jpg
Figure 3-23

Selectin the DB project as a project reference

Right-click the Solution and select Manage NuGet Packages for Solution, as shown in Figure 3-24.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig24_HTML.jpg
Figure 3-24

Bring up the Manage NuGet Packages for Solution dialog

Install the following packages to both projects:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
Install the following packages to the starter project [Activity0302_EFCoreNewDb]:
Microsoft.EntityFrameworkCore.Design
Microsoft.Extensions.Configuration
Install the following packages to the DB project [InventoryDatabaseCore]:
Microsoft.EntityFrameworkCore.Tools
Installing NuGet packages is shown in Figure 3-25.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig25_HTML.jpg
Figure 3-25

Installing NuGet packages to the projects

In the end, you should have the following entries as shown in Figure 3-26 in the starter project file.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig26_HTML.jpg
Figure 3-26

The package and project references are shown in the .csproj file

And the DB project should have the entries as shown in Figure 3-27.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig27_HTML.jpg
Figure 3-27

The DB project package references

Next, rename the class in the InventoryDatabaseCore project from Class1.cs to InventoryDbContext.cs. When prompted, select Yes to allow renaming (as seen in Figure 3-28).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig28_HTML.jpg
Figure 3-28

Renaming the Class1.cs file to be our new InventoryDbContext

Change the class code in the InventoryDbContext to be a DB Context type and implement a constructor to allow injection of DBContextOptions:
public class InventoryDbContext : DbContext
{
    public InventoryDbContext(DbContextOptions options)
        : base()
    {
    }
}
Add an appsettings.json file to each project. In the file, add the connection string to connect to your database. Use .\SQLExpress if you are using SQLExpress or localhost if you are using SQLDeveloper edition.
{
  "ConnectionStrings": {
    "InventoryManager": "Data Source=.\SQLExpress;Initial Catalog=InventoryManager;Trusted_Connection=True"
  }
}
Figure 3-29 shows what an appsettings.json file should look like with an active connection string and also highlights the two locations to add the new file.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig29_HTML.jpg
Figure 3-29

Setting the connection string in appsettings.json

Make sure to set the appsettings.json files as Copy to Output Directory ➤ Copy if newer for the starter project EFCoreNewDb (as shown in Figure 3-30).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig30_HTML.jpg
Figure 3-30

Setting the appsettings.json file as Copy if newer for deploy

In the starter project, add a new file: ConfigurationBuilderSingleton.cs. In the new file, place the code as follows:
public sealed class ConfigurationBuilderSingleton
{
    private static ConfigurationBuilderSingleton _instance = null;
    private static readonly object instanceLock = new object();
    private static IConfigurationRoot _configuration;
    private ConfigurationBuilderSingleton()
    {
        var builder = new ConfigurationBuilder()
                            .SetBasePath(Directory.GetCurrentDirectory())
                            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
        _configuration = builder.Build();
    }
    public static ConfigurationBuilderSingleton Instance
    {
        get {
            lock (instanceLock)
            {
                if (_instance == null)
                {
                    _instance = new ConfigurationBuilderSingleton();
                }
                return _instance;
            }
        }
    }
    public static IConfigurationRoot ConfigurationRoot
    {
        get
        {
            if (_configuration == null) { var x = ConfigurationBuilderSingleton.Instance;  }
            return _configuration;
        }
    }
}
After adding the singleton class, change the program.cs file to contain the following code:
class Program
{
    static IConfigurationRoot configuration;
    static DbContextOptionsBuilder<InventoryDbContext> _optionsBuilder;
    static void Main(string[] args)
    {
        BuildOptions();
        Console.WriteLine(_configuration.GetConnectionString("InventoryManager"));
        ListInventory();
    }
    static void BuildOptions()
    {
        _configuration = ConfigurationBuilderSingleton.ConfigurationRoot;
        _optionsBuilder = new DbContextOptionsBuilder<InventoryDbContext>();
        _optionsBuilder.UseSqlServer(_configuration.GetConnectionString("InventoryManager"));
    }
    static void ListInventory()
    {
    }
}
Run the program to validate that everything is set up and that your connection string is printed out as expected. Figure 3-31 shows the expected output of the connection string (your output may vary if your connection string is different than mine).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig31_HTML.jpg
Figure 3-31

The program is set up and working as expected

As a final reminder, when adding a lot of code such as we’ve done in this activity in the last two steps, if something goes wrong and you are getting a lot of errors, don’t forget to compare your code to the final version of the files.

As you are already sure your project is working as expected, skip to step 3.

Step 2: Make sure your project is set up correctly

As mentioned previously, the easiest approach for starting this activity is to get the starter files for the activity. Once you have the files, open the project, then make sure the connection string is set to match your local environment database, and finally run the program to make sure it works as expected.

When the program is working as expected, the output should be similar to what is shown in Figure 3-31 at the end of step 2.

Step 3: Add a reusable library for our database models – the “code” of code first

While it is entirely possible to put your code in the same location as the context, it is much more flexible for future use if we separate the models to their own class. Right-click the Solution, and then select Add ➤ New Project. Use the Class Library (.NET Core) project template. Name the new project InventoryModels and save the project in the same directory as your other projects in the solution.

After creation, the project should look as follows in Figure 3-32.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig32_HTML.jpg
Figure 3-32

Creating the class library for the inventory system models

Rename Class1 to Item.cs. When prompted, select Yes to rename. In the Item.cs file, add a public property for Id as int and Name as string. We will modify this class in more detail later in the book, but we’ll keep it simple here.
public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Next, reference the InventoryModels project in the InventoryDatabaseCore Library project. Right-click the InventoryDatabaseCore project, select Add ➤ Reference, and then select the InventoryModels project as a project reference as shown in Figure 3-33.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig33_HTML.jpg
Figure 3-33

Referencing the InventoryModels library in the InventoryDatabaseCore library

Step 4: Reference the library in an entity DbSet

This next step is one of the most critical steps in the process. If we forget to do this, our migration will scaffold successfully with nothing to update in the database, creating a blank migration. Therefore, if you run the add-migration command after creating a model, and the migration is blank, consider checking your DbContext to make sure you included a reference to the DBSet.

In the file InventoryDbContext in the InventoryDatabaseCore library, add the following line of code:
public DbSet<Item> Items { get; set; }
Adding this line of code will require a using statement to reference the Models project using InventoryModels. See Figure 3-34 for more clarity.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig34_HTML.jpg
Figure 3-34

Adding the DbSet<Item> to the Inventory DB Context

Step 5: Add a new migration

We are now ready to begin creating migrations for our new database. To do this, after ensuring that we have set everything up to this point as covered, we can run the command add-migration “Initial Migration” in the PMC.

Make certain the Default project drop-down is pointing to the InventoryDatabaseCore project. Once this is in place, run the command as shown earlier for the initial migration. Review Figure 3-35 for more clarity.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig35_HTML.jpg
Figure 3-35

Adding the initial migration for our new database creates an error

Note that this generated an error. The error is “Unable to create an object of type ‘yourdbcontext’…”. The error is not very helpful as to what went wrong.

This error is happening because we do not have a default constructor in the context file. Let’s add a default constructor and try again.

Use the following code to add a new default constructor:
public InventoryDbContext() : base() { }. Additionally, make sure to add the options in as a parameter to the base method in the explicit constructor:
public InventoryDbContext(DbContextOptions options)
    : base(options)
{
}
Review Figure 3-36 to see the code as placed into the file.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig36_HTML.jpg
Figure 3-36

Fixing the class with a default constructor

Now that this is fixed, try to add the migration again. Do you think it will work this time?

If you guessed “no,” you get a prize. The prize is yet another error (see Figure 3-37).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig37_HTML.jpg
Figure 3-37

Another error. No database provider has been configured for this DbContext

The text of this error is actually “No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.”

So how do we fix this issue?

As it turns out, we need to do what it says in the error. The easiest solution is simply to override the OnConfiguring method. In fact, if we go back to activity one from this chapter and look at the context that was generated, we’ll see exactly the code that we need. Review Figure 3-38 to see the code from the OnConfiguring method in activity 0301.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig38_HTML.jpg
Figure 3-38

Activity 0301 had placed an override for OnConfiguring in the DbContext

A couple of important notes. This code from activity 0301 is exactly the same as the options builder that we are using in our startup project. In this case, however, the startup project was not run, so the context did not get an options builder injected (remember, we added a default constructor that takes no parameters in order to run migrations). Therefore, the context itself needs to know how to connect to the database.

Another important note is the fact that the context is literally begging us to change out the code so that we don’t use a hard-coded connection string. While we could get away with that for now, we really should update it to prevent having our connection string information in code (eventually we’ll need to connect to a production database with something other than the trusted windows login).

Let’s add the code for the OnConfiguring method to our DbContext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseSqlServer("Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True");
    }
}

Next, let’s remove the direct reference to the connection string. The good news is that we already have an appsettings.json file in place. Now we just need to leverage that from our database context.

Just as we did in the startup, we’ll need to build out the connection string from the builder. If we were using an Asp.Net core application, we could leverage the services and just work through that, but from this console project, it’s a bit trickier.

Also, we can’t reference the startup project static builder that we built, because that would create a circular dependency. So, let’s just rehash the builder code directly.

In the InventoryDatabaseContext, add the following code directly into the OnConfiguring override method:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        var builder = new ConfigurationBuilder()
                        .SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
        _configuration = builder.Build();
        var cnstr = _configuration.GetConnectionString("InventoryManager");
        optionsBuilder.UseSqlServer(cnstr);
    }
}

You’ll also need to reference any missing using statements such as using Microsoft.Extensions.Configuration; and using System.IO; at the top of the file.

Additionally, you’ll need to add a class-level variable before the Main method: static IConfigurationRoot _configuration;

Let’s try that add-migration command one more time (see Figure 3-39).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig39_HTML.jpg
Figure 3-39

Adding the migration succeeds now that we have access to the configuration and have correctly set up the database context to run with a code-first approach

Step 6: Updating the database

Now that we have an initial migration in place, we are ready to update our local database. Let’s see what happens if we just run the update-database command in the PMC. The successful command execution is shown in Figure 3-40.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig40_HTML.jpg
Figure 3-40

Updating the database was successful

There is a chance this will not work for you out of the box. Depending on your local instance and how things are configured, you may need to make sure that you can connect with local windows accounts and/or set to mixed mode if you want to use a SQL Server user id and password.

In some rare instances, you may need to create the database yourself and then run the update-database command . In the end, you should be able to get the update-database command to work as expected.

Examining the database using SQL Server Management Studio (SSMS) proves the database exists as we would expect (see Figure 3-41).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig41_HTML.jpg
Figure 3-41

Examining the newly created database

Step 7: Insert and retrieve a set of items

As a final part to this activity, let’s insert and retrieve some items from the Items table in our database. This part of the activity is optional, as we’ll be covering how to work with the database in more detail later in the book. However, I’m a big believer in taking things one step at a time, and this simple insert and retrieve is a good way to get our feet wet.

If you consider yourself to be well versed in EF and LINQ, perhaps step away from the activity and attempt to write the insert and read/write of about five items to and from the Items table.

To begin, in the console startup, let’s add a method to populate the database. This method should be called InsertItems. Add any using statements for System.Collections.Generic and the InventoryModels project as needed. In the method, we’ll get an instance of the context and then insert a prefabricated list of items. The InsertItems method should be added anywhere in the Program class. I generally just add to the end when adding new methods.
static void InsertItems()
{
    var items = new List<Item>() {
        new Item() { Name = "Top Gun" },
        new Item() { Name = "Batman Begins"},
        new Item() { Name = "Inception" },
        new Item() { Name = "Star Wars: The Empire Strikes Back"},
        new Item() { Name = "Remember the Titans"}
    };
    using (var db = new InventoryDbContext(_optionsBuilder.Options))
    {
        db.AddRange(items);
        db.SaveChanges();
    }
}
Additionally, let’s remove the printout of the connection string and replace with a call to the InsertItems method within the Main method:
static void Main(string[] args)
{
    BuildOptions();
    InsertItems();
    ListInventory();
}

By doing this, we’ll quickly discover how poor our initial DB design is, as there will be many things we want to enhance about the Items table.

Also note, we are not going to prevent duplicates for now. As stated, we’ll learn more in the future about how to do things correctly against the database.

Finally, let’s query the database and output each returned item, taking just the top five for now since we know that if we run this method multiple times, there will be duplicates inserted on every run. Add a new method called ListInventory within the Program class, following the InsertItems method.
static void ListInventory()
{
    using (var db = new InventoryDbContext(_optionsBuilder.Options))
    {
        var items = db.Items.Take(5).OrderBy(x => x.Name).ToList();
        items.ForEach(x => Console.WriteLine($"New Item: {x.Name}"));
    }
}
Run the application. The results should be similar to what you see in Figure 3-42.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig42_HTML.jpg
Figure 3-42

The output of our first code-first application

Final thoughts

In this activity, we have worked through the steps that it takes to get a new project up and running with the EFCore code-first approach. By now, you should be very familiar with the idea of how code-first database interaction works. However, it’s always a great idea to highlight and review our takeaways from this learning activity.
  • The DbContext is the controller for interaction with the database. All the information we need is handled by the context, including DbSets, and configuration to connect.

  • The Model classes dictate how the table will be structured. Adding public properties will generate columns in the tables.

  • Add the model to the context as a DbSet in order to make sure the migration includes that model in the scaffolding.

  • For the migrations to work, we need the Microsoft.EntityFrameworkCore.Tools package and we need to override the OnConfiguring method in the DbContext to make sure the database connection is set as expected. Additionally, the startup project needs the package Microsoft.EntityFrameworkCore.Design.

  • We create the migration with the command add-migration [migration name] and then run update-database to execute the migration against the database

  • Migrations are tracked in the database in the table __EFMigrationsHistory

    One final note is that we will be leveraging this project for much of the remainder of the book.  If you are fluent with source control, now would be a good time to add your project to a repository so that you can easily work with it in the future.

Activity 0303: Creating a code-first project in EF6

In the final activity for this chapter, we’re going to implement an EF6 code-first implementation against an existing project that uses classic ADO.Net as its data access layer. In the real world, your existing project may have another version of Entity Framework already in place (EF2, EF3, EF4, EF5) and may just need a few tweaks to update to use EF6. Additionally, another path with a project that has an existing older EF implementation could just build a data access library for new functionality in EF6. However, it is likely that if you need to do this sort of upgrade, it is to bring some older application up to the last LTS version of the .Net Framework and EF6 to extend its life with the best security and architecture possible, in a manner that is much less expensive than a full rewrite.

Why not a new project?

You could create a new project with a new .Net 4.8 and EF6 architecture implementation. However, if a new project is going to be built, I encourage you to build it in the latest version of Core or in .Net 5 (vNext), depending on when you are reading this book.

Using an existing project to implement an EF6 code-first approach

Before we begin, it’s important to note that there will be a few major differences in how this works from what we’ve learned in the EFCore activities, as working with EF6 and the database context with migrations has a couple of minor differences. Additionally, while the overall idea remains the same, a couple of the commands are different in the PMC for EF6 vs. Core.

Pre-activity setup

To get started with this activity, it will be easiest to just grab a copy of the starter files Activity0303_EF6_UpdateFromExisting_Starter. Extract the files and work along with me as we build out the solution using EF6 code first.

Step 1: Configure the connection if necessary and run the project

As with other projects, this code is pointed at our restored version of AdventureWorks and should use a similar connection string. Make sure the connection string is set up correctly and run the project. You should see output similar to what is shown in Figure 3-43.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig43_HTML.jpg
Figure 3-43

Initial output from a pseudo-legacy application against the AdventureWorks database

Step 2: Create a new library and add the Entity Framework libraries

As with other projects, we’re going to create a new library and then add the Entity Framework libraries to this project. We’ll also need to add the Entity Framework libraries to the startup project. Begin by adding the new library project and naming it something like AWEFDataAccessLayer (see Figure 3-44).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig44_HTML.jpg
Figure 3-44

The new data access layer library for our existing project

Next, add the EntityFramework NuGet Package to both the new DAL project and the original startup project as shown in Figure 3-45.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig45_HTML.jpg
Figure 3-45

Installing Entity Framework to the library and the startup project

Step 3: Delete the EFMigrations History table from the AdventureWorks database

If you completed the previous activities in this chapter, you should already have an EFMigrations table in the AdventureWorks database. You’ll want to make sure to delete this table before running the wizard in step 4. You can easily delete the table by right-clicking and selecting Delete in the SSMS. Another solution would be to script the table as delete and then execute the script. A third solution would be to restore the backup to a new instance of the AW database and then just point to that new instance in this activity. A final solution would be to just leave it but remove it from the generated context and code after running the wizard in the next step. Figure 3-46 shows how to delete the migrations table.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig46_HTML.jpg
Figure 3-46

Deleting the previously created migrations table

Step 4: Create the code-first implementation

Thinking back to our EF6 implementation against the existing database from Chapter 2, we had an auto-generated context that was built out for us. With this project, we will not end up with an EDMX file that will generate code for us. Instead, we’re going to have regular model files and a context which we’ll be able to leverage going forward.

To begin, right-click the project and select AddNew Item (see Figure 3-47).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig47_HTML.jpg
Figure 3-47

Add a new item to your data access layer library project

In the Add New Item dialog, on the left navigation pane, select Data. Then select ADO.Net Entity Data Model. Name the model AdventureWorks and select Add. Figure 3-48 shows the Add New Item dialog.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig48_HTML.jpg
Figure 3-48

Creating the new code-first data model

In the Entity Data Model Wizard, select Code First from database (see Figure 3-49).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig49_HTML.jpg
Figure 3-49

Selecting the Code First from database option

If you’ve worked through other activities, you likely already have a connection to choose from. If not, you’ll need to create a new connection. If creating the new connection, review the EF6 activity against an existing database from Chapter 2 for more information. Once you have the connection, select it. Name your connection in the App.Config as the default, which is the name of your model. In my case, this is AdventureWorks. There are some slight differences that we’ll want in this connection string, so you can name this connection string anything but AdventureWorksDb, which is the configuration string name for the classic database instance in these activity files. Setting up the configuration string is shown in Figure 3-50.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig50_HTML.jpg
Figure 3-50

Setting the connection and connection string properties

Once the database connection is set, you’ll get the options for what tables and views to include. Since we’ll be doing an initial migration and we want to ultimately squelch all of this from being re-created, make sure to just select all of the tables and views (see Figure 3-51).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig51_HTML.jpg
Figure 3-51

Selecting all the database objects for creation

Leave the box to Pluralize or singularize checked, and then select Finish. In the end, you’ll have a DBContext file and a bunch of model files.

Figure 3-52 shows the AdventureWorks DBContext. Make note of the way the connection string is directly passed to this context by name (as opposed to builder options as in EFCore).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig52_HTML.jpg
Figure 3-52

A quick look at the generated DBContext

Also make note of how the views are referenced in the generated context (see Figure 3-53).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig53_HTML.jpg
Figure 3-53

A look at how the views are defined in the generated DBContext

You should also note all of the other models that are defined as expected and the lack of an EDMX file.

Step 5: Enable migrations

Now that we have the database structure defined with models and the DBContext, it’s time to generate the initial migration.

Remember that any time our database models or context have changed, it’s important to rebuild the project and make sure there are no errors. You should have no errors at this point. However, make sure to build now, and then fix any errors if any exist. We cannot generate migrations if the project will not build.

Next, open the PMC, then point to the EFDataAccessLayer project as the default, and run the command add-migration “Initial Migration” to see what happens (review Figure 3-54).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig54_HTML.jpg
Figure 3-54

Attempting to add the initial migration in our EF data access layer library

Here, we see there is an error, telling us that no configuration type is found. To fix this, we need to run the command enable-migrations. However, before we do that, we need to consider what we are going to enable. Enabling migrations can be configured to run automatically (on project startup) or can be left as a manual operation (requires intervention). On a new instance, automatic migrations are what we likely want. In this case, however, we need to be extremely careful as automatic migrations may remove or delete objects or data from our existing database. Therefore, we’ll want to make sure automatic migrations are not enabled. We can check and easily change this setting later in the DBContext file.

Run the command enable-migrations in the PMC to see what happens. The output with an error is shown in Figure 3-55.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig55_HTML.jpg
Figure 3-55

Error for no connection string in the application config file

This error might be particularly confusing. If we look at the app.config file in our library, the connection string is definitely there. However, the config for the console startup project does not have this connection string.

Copy the connection string from the configuration file in the library to the startup project. For convenience, the code is shown first here, and then the placement is shown in Figure 3-56.
<connectionStrings>
  <add name="AdventureWorks"
       connectionString="data source=localhost;initial catalog=AdventureWorks;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
       providerName="System.Data.SqlClient" />
</connectionStrings>
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig56_HTML.jpg
Figure 3-56

Copying the connection string to the startup project app.config file

One more important note is that there is an additional section added to the config file in both projects when we referenced the Entity Framework packages. If this section is missing, EF will likely not work. Make sure both config files have the following config sections:
<configSections>
  <section name="entityFramework"
           type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
           requirePermission="false" />
</configSections>
<entityFramework>
  <providers>
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
  </providers>
</entityFramework>
The placement of the code is shown in Figure 3-57.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig57_HTML.jpg
Figure 3-57

Validate the config sections and Entity Framework entries in each project’s config file

If for some reason this section of the config is messed up, try to remove and then reinstall the Entity Framework libraries to the project. That should clear up the config file. Also note that you’ll have two connection strings in the App.config file for the main project as the legacy code connection already existed.

Once again, run the command enable-migrations to see what happens (see Figure 3-58).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig58_HTML.jpg
Figure 3-58

Migrations are now enabled

With the migrations enabled, we can see that there is a folder “Migrations” in the AWEFDataAccessLayer project. If we look at the configuration file, we can see the line for enabling the migrations right in the constructor (review Figure 3-59).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig59_HTML.jpg
Figure 3-59

The configuration file for our migrations is now in place

Note the line for AutomaticMigrationsEnabled = false. If we want to turn automatic migrations on, we can set this option to true. We could have also used that as a flag in the enable-migrations command with the command Enable-Migrations -EnableAutomaticMigrations.

Step 6: Create the initial migration

In some cases, the initial migration may already have been created for you. In our case, it is likely we need to generate it.

Continue to make sure your project builds and that you are in the PMC pointing to the default project of your AWEFDataAccessLayer.

The next part of this activity is only going to happen if you are working against all the data in AdventureWorks or in any other database which includes geography data. I opted to keep this spatial data in for this example, even though referencing geography data will create a number of additional problems for us to solve.

Run the command add-migration “Initial Migration” to see what happens.

If you don’t get an error for spatial types, skip to step 8.

Here, if you do get a geography error, as shown in Figure 3-60, we’ll need to fix it.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig60_HTML.jpg
Figure 3-60

Spatial types geography error

This error might happen if you are using an older version of EF6, such as a version less than or equal to version 6.3. This error should not happen on version 6.4+ of EF6.

To fix this error, install the NuGet package Microsoft.SqlServer.Types on the AWEFDataAccessLayer project and the startup console project (see Figure 3-61).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig61_HTML.jpg
Figure 3-61

Installing the Microsoft SqlServer Types library

Make sure to rebuild the solution after adding NuGet packages.  Failure to rebuild the solution will not allow the PMC to recognize new NuGet packages.  Once the solution is rebuilt, the package(s) will be recognized in the PMC.

In order for our migrations to work, we need to tell Entity Framework to use this spatial types provider. We might be able to map it in the providers section of the app.config file. However, a guaranteed way to make this work is to add the following two lines of code to the constructor of the AdventureWorks context:
SqlProviderServices.SqlServerTypesAssemblyName =
    "Microsoft.SqlServer.Types, Version=14.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91";    SqlServerTypes.Utilities.LoadNativeAssemblies(AppDomain.CurrentDomain.BaseDirectory);
Upon completion, this would look similar to what is shown in Figure 3-62.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig62_HTML.jpg
Figure 3-62

Setting the provider in the constructor of the context

Once again, run the command add-migration “Initial Migration” to see what happens. When successful, the new migration should be generated.

Step 8: Comment all “Up” code, delete all “Down” code, update the database

As in the previous activity (activity 0302), we don’t want to re-create all the tables (running would likely error and let us know that the objects already exist). Therefore, we need to clean up our initial migration.

Start by commenting the code for the initial migration’s Up() method. Likely, we will never need or want this code. However, in case the code needs to be restored for some reason, we will still have it (see Figure 3-63).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig63_HTML.jpg
Figure 3-63

Comment out (or remove) all code in the “Up” method for the initial migration

There should not be a situation where we ever want to run the down code on this migration, as it would be highly destructive, so just remove any code and replace with a comment to note the intentional removal of code (see Figure 3-64).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig64_HTML.jpg
Figure 3-64

Remove the “Down” method code for the initial migration

After cleaning up the initial migration, build the project. Then run the update-database command in the PMC. Figure 3-65 shows the successful update of the database with the new migration.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig65_HTML.jpg
Figure 3-65

Updating the database

Validate that the next migration does not require the models again by adding another migration with the command add-migration "test scaffolding" to see what happens (as shown in Figure 3-66).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig66_HTML.jpg
Figure 3-66

Making sure the next migration scaffolds with no model changes

Since this new migration is blank, we know we are not going to cause any issues, and we can just delete the test migration. Right-click the file and select Delete. If for some reason you had applied this migration, just revert with the command update-database -targetmigration InitialMigration and then delete the test migration.

Step 9: Leverage the new context from the startup app

Now that our context is wired up, we can leverage it from the regular code in our startup application.

Reference the new library project in the console startup application (shown in Figure 3-67).
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig67_HTML.jpg
Figure 3-67

Referencing the new EF library in the startup project

Add a new method called GetPeopleEF to the Program class, which returns a list of Person, and is coded as follows:
private static List<Person> GetPeopleEF()
{
    var people = new List<Person>();
    using (var context = new AdventureWorks())
    {
        var result = context.People
                            .Where(x => x.LastName.StartsWith("G"))
                            .Take(5)
                            .OrderBy(x => x.LastName).ToList();
        foreach (var p in result)
        {
            var person = new Person()
            {
                BusinessEntityID = p.BusinessEntityID,
                FirstName = p.FirstName,
                LastName = p.LastName,
                PersonType = p.PersonType,
                Suffix = p.Suffix,
                Title = p.Title
            };
            people.Add(person);
        }
    }
    return people;
}

Note that we’ll need to set a using statement to map Person now, since there is a conflict between our original DAL and the new EF DAL. Assuming all functional UI will rely on the original mappings, we’ll have to map our EF objects back to DAL objects to ensure we don’t break legacy applications. The using statement for Person is as follows: using Person = AWDataLayerObjects.Person;

In case you’ve never seen a using statement assigned to a variable before, review Figure 3-68 to see what the code should look like.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig68_HTML.jpg
Figure 3-68

The using statement to eliminate ambiguity for the Person object

Finally, to complete the program, add two lines of code to the program.cs Main method:
var efPeople = GetPeopleEF();
efPeople.ForEach(p => Console.WriteLine($"Next Person: {p.GetFullName()}"));

This code should come right before the completion of the program.

Run the program to see it in action. Your output should be similar to the output in Figure 3-69.
../images/491632_1_En_3_Chapter/491632_1_En_3_Fig69_HTML.jpg
Figure 3-69

The final output of the program

Final thoughts

In this final activity, we have worked through creating a new code-first EF6 library against an existing database. Creating against a new database would be a similar process, although creating new EF6 projects is likely something you will not be doing very much of in the future. If a new database and project are being built, you should consider moving to EFCore or .Net vNext (.Net 5+).

As a reminder, there are some major differences when using EF6 in a code-first manner. We saw a few of them in play in this activity.
  • Code-first migrations require a configuration file.

  • The configuration file must have migrations enabled.

  • Migrations can be automatic or manual, based on a flag in the configuration file.

  • Use the command add-migration [migration name] to scaffold a new migration.

  • Use the command update-database to apply the next migration.

  • Use the command update-database -targetmigration [migration name] to roll back the database to a previous migration.

  • EF6 implementations have a much more verbose config file.

  • EF6 implementations generally require the connection string to specify that multiple active result sets are allowed.

Final thoughts for this chapter

In this chapter, we have taken an in-depth look at how we can get different projects set up to work with the code-first approach to database development.

We have also gained a basic understanding of what it takes to make changes via migrations when working with the code-first approach.

We also saw that getting things to work manually can sometimes be excessively painful, while somehow beautiful. There is a certain reassurance that happens when you know for sure that your code and your database are completely in sync with each other. We also saw that while there were errors during setup, they were easily overcome, and we are prepared to set up projects of all types in the future.

Our key takeaways for this chapter are
  • Code-first migrations can be applied to any project, at any stage of maturity.

  • Models are the key to generating database objects and working with the data in code.

  • The DBContext acts as a definition for all database objects available.

  • A DbSet<T> is essentially a table of the object defined in its generic type T.

  • Migrations need to be scaffolded.

  • Migrations can be applied in a forward or backward direction.

  • Migrations are tracked in the database.

  • Code-first development allows any developer to quickly build out a copy of the database by structure on any machine.

  • Code-first development could be considered as an imperative approach to database programming.

Final thoughts on section 1

In these first three chapters, we’ve seen what it takes to work with Entity Framework in both the classic manner (.Net Framework <= 4.8 and EF6) and in the new manner (.Net Core 3.0/EFCore 3.0). This was a necessary thing to cover because as developers we will likely encounter both versions for many years to come in legacy apps and new development.

For the remainder of the book, we’ll be focused on EFCore and vNext Entity Framework concepts and database programming. Even so, almost every concept we learn will still be relevant, even to the EF6 implementations, even if the older versions have a small difference in implementation or syntax.

Now that we have a very good understanding of how to get projects up and running with Entity Framework, in the next section, we’ll start diving deeper into building out the data solution, beginning with a deeper look at models, contexts, and migrations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.175.182