Entity Framework (5), With .Net Core MVC, Database-First

After I wrote several articles on this site, I found out it seemed almost for every article, I needed to set up a sample application associated with an entity framework if accessing the database. And, every time, I needed to rewrite the setup process from scratch in order for a new reader to follow along easily. Even for introducing a very simple concept, such as Caching, I needed to spend 80% of the time setting up the sample app, and only 20%  on introducing the Caching concept itself.
 
Therefore, I think it is better to write a basic model such as entity framework sample for various approaches, and then I can reuse them when needed. I made a list of the series of articles below, I will write them one by one, while the Entity framework overview and concept will be covered in the article (0):
Note
We write the Entity Framework for MVC module, but the pattern is the same or similar when applying to Web Application or Web API.
 

Introduction

 
This article is about Entity Framework with .Net Core MVC, Database-First approach. 
  • Step 1: Create an ASP.NET Core MVC application
  • Step 2: Reverse engineer Entity model from database (database first aproach for entity)
  • Step 3: Scaffold Controller with View using Entity Framework
  • Step 4: Run and Test app
At the end, you have an MVC app that can consume a database directly through entity framework.
 
Note
This article is actually from my previous article Part A: Build AS.P.NET MVC in .NET Core with Entity Framework Database First.
 

Step 1: Create an ASP.NET Core MVC application

 
We use the current version of Visual Studio 2019 16.8 and .NET 5.0 SDK to build the app.
  1. Start Visual Studio and select Create a new project.
  2. In the Create a new project dialog, select ASP.NET Core Web Application > Next.
  3. In the Configure your new project dialog, enter MVCCallWebAPI for Project name.
  4. Select Create.
  5. In the Create a new ASP.NET Core web application dialog, select,

    1. .NET Core and ASP.NET Core 5.0 in the dropdowns.
    2. ASP.NET Core Web App (Model-View-Controller).
    3. Create
 
Build and run the app, you will see the following image shows the app,
 
 

Step 2: Reverse engineer Entity model from database (database first aproach for Entity)

 
We use a local Microsft SQL server, and the sample database pubs and its table stores as our database sample. We try to reverse engineer to get the table Stores into the project and make an entity model Store.
 
Click "Tools->NuGet Package Manager->Package Manager Console" as shown below i.e.
 
 
This is the reverse engineering command (when you run the command in PMC, you need to make it in one line),
  1. Scaffold-DbContext "Data Source=localhost;Initial Catalog=pubs;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False" Microsoft.EntityFrameworkCore.SqlServer   
  2. -OutputDir Models/DB   
  3. -Table dbo.stores  
Run the command in the PMC,
 
 
We got error message above that Microsoft.EntityFrameworkCore.Design is required, but not installed. Click "Tools->NuGet Package Manager->Manage NuGet Packages for Solution" as shown below,
 
 
Choose and install: Microsoft.EntityFrameworkCore.Design,
 
 
Run the PMC command again, 
 
 
We got: Unable to find provider assembly 'Microsoft.EntityFrameworkCore.SqlServer', install it in the same way above from Manage NuGet Packages for Solution, and then reRun PMC command. This was successful and two classes are reverse engineered under Models/DB as shown below: pubsContext.cs and Store.cs
 
 

Step 3: Add Controller with View using Entity Framework

 
For adding controller using entity framework, we need to modify the reverse engineered classes pubsContext and Store.cs. 
 
1. Modify the data connection
 
For the class pubsContext, we need to comment out the data connection part, 
  1. //        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)  
  2. //        {  
  3. //            if (!optionsBuilder.IsConfigured)  
  4. //            {  
  5. //#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.  
  6. //                optionsBuilder.UseSqlServer("Data Source=localhost;Initial Catalog=pubs;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");  
  7. //            }  
  8. //        }  
and move the data connection string into file appsettings.json,
  1. {        
  2.   "Logging": {        
  3.     "LogLevel": {        
  4.       "Default""Information",        
  5.       "Microsoft""Warning",        
  6.       "Microsoft.Hosting.Lifetime""Information"        
  7.     }        
  8.   },        
  9.         
  10.   "ConnectionStrings": {        
  11.     "DevConnection""Data Source=localhost;Initial Catalog=pubs;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"        
  12.   },        
  13.       
  14.   "AllowedHosts""*"        
  15. }    
Register the database connection context into Class starup.cs inside ConfigureServices,
  1. public void ConfigureServices(IServiceCollection services)        
  2. {        
  3.     // Register SQL database configuration context as services.         
  4.     services.AddDbContext<pubsContext>(options =>        
  5.     {        
  6.         options.UseSqlServer(Configuration.GetConnectionString("DevConnection"));        
  7.     });        
  8.         
  9.     services.AddControllersWithViews();        
  10. }    
Otherwise,  we could make a controller with view using this entity framework, and this would not work.
 
2. Modify the model
 
In class pubsContext, we can also comment out the data constrain part, 
  1. //protected override void OnModelCreating(ModelBuilder modelBuilder)    
  2. //{    
  3. //    modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");    
  4.     
  5. //    modelBuilder.Entity<Store>(entity =>    
  6. //    {    
  7. //        entity.HasKey(e => e.StorId)    
  8. //            .HasName("UPK_storeid");    
  9.     
  10. //        entity.ToTable("stores");    
  11.     
  12. //        entity.Property(e => e.StorId)    
  13. //            .HasMaxLength(4)    
  14. //            .IsUnicode(false)    
  15. //            .HasColumnName("stor_id")    
  16. //            .IsFixedLength(true);    
  17.     
  18. //        entity.Property(e => e.City)    
  19. //            .HasMaxLength(20)    
  20. //            .IsUnicode(false)    
  21. //            .HasColumnName("city");    
  22.     
  23. //        entity.Property(e => e.State)    
  24. //            .HasMaxLength(2)    
  25. //            .IsUnicode(false)    
  26. //            .HasColumnName("state")    
  27. //            .IsFixedLength(true);    
  28.     
  29. //        entity.Property(e => e.StorAddress)    
  30. //            .HasMaxLength(40)    
  31. //            .IsUnicode(false)    
  32. //            .HasColumnName("stor_address");    
  33.     
  34. //        entity.Property(e => e.StorName)    
  35. //            .HasMaxLength(40)    
  36. //            .IsUnicode(false)    
  37. //            .HasColumnName("stor_name");    
  38.     
  39. //        entity.Property(e => e.Zip)    
  40. //            .HasMaxLength(5)    
  41. //            .IsUnicode(false)    
  42. //            .HasColumnName("zip")    
  43. //            .IsFixedLength(true);    
  44. //    });    
  45.     
  46. //    OnModelCreatingPartial(modelBuilder);    
  47. //}    
  48.     
  49. //partial void OnModelCreatingPartial(ModelBuilder modelBuilder);    
but, we need to modify the data model to make the table member names exactly the same as they are in database, such as StorId into Stor_Id, and add a  [Key] for primary key in database.
 
The class Store.cs, before,
  1. using System.ComponentModel.DataAnnotations;    
  2.   
  3. #nullable disable    
  4.     
  5. namespace MVCCallWebAPI.Models.DB    
  6. {    
  7.     public partial class Store    
  8.     {    
  9.         public string StorId { getset; }    
  10.         public string StorName { getset; }    
  11.         public string StorAddress { getset; }    
  12.         public string City { getset; }    
  13.         public string State { getset; }    
  14.         public string Zip { getset; }    
  15.     }    
  16. }     
After
  1. using System.ComponentModel.DataAnnotations;    
  2.   
  3. #nullable disable    
  4.     
  5. namespace MVCCallWebAPI.Models.DB    
  6. {    
  7.     public partial class Store    
  8.     {    
  9.         [Key]    
  10.         public string Stor_Id { getset; }    
  11.         public string Stor_Name { getset; }    
  12.         public string Stor_Address { getset; }    
  13.         public string City { getset; }    
  14.         public string State { getset; }    
  15.         public string Zip { getset; }    
  16.     }    
  17. }    
The final class pubsContext will be,
  1. using Microsoft.EntityFrameworkCore;  
  2.  
  3. #nullable disable  
  4.   
  5. namespace MVCCallWebAPI.Models.DB  
  6. {  
  7.     public partial class pubsContext : DbContext  
  8.     {  
  9.         public pubsContext()  
  10.         {  
  11.         }  
  12.   
  13.         public pubsContext(DbContextOptions<pubsContext> options)  
  14.             : base(options)  
  15.         {  
  16.         }  
  17.   
  18.         public virtual DbSet<Store> Stores { getset; }  
  19.   
  20. //        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)  
  21. //        {  
  22. //            if (!optionsBuilder.IsConfigured)  
  23. //            {  
  24. //#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.  
  25. //                optionsBuilder.UseSqlServer("Data Source=localhost;Initial Catalog=pubs;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");  
  26. //            }  
  27. //        }  
  28.   
  29.         //protected override void OnModelCreating(ModelBuilder modelBuilder)  
  30.         //{  
  31.         //    modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");  
  32.   
  33.         //    modelBuilder.Entity<Store>(entity =>  
  34.         //    {  
  35.         //        entity.HasKey(e => e.StorId)  
  36.         //            .HasName("UPK_storeid");  
  37.   
  38.         //        entity.ToTable("stores");  
  39.   
  40.         //        entity.Property(e => e.StorId)  
  41.         //            .HasMaxLength(4)  
  42.         //            .IsUnicode(false)  
  43.         //            .HasColumnName("stor_id")  
  44.         //            .IsFixedLength(true);  
  45.   
  46.         //        entity.Property(e => e.City)  
  47.         //            .HasMaxLength(20)  
  48.         //            .IsUnicode(false)  
  49.         //            .HasColumnName("city");  
  50.   
  51.         //        entity.Property(e => e.State)  
  52.         //            .HasMaxLength(2)  
  53.         //            .IsUnicode(false)  
  54.         //            .HasColumnName("state")  
  55.         //            .IsFixedLength(true);  
  56.   
  57.         //        entity.Property(e => e.StorAddress)  
  58.         //            .HasMaxLength(40)  
  59.         //            .IsUnicode(false)  
  60.         //            .HasColumnName("stor_address");  
  61.   
  62.         //        entity.Property(e => e.StorName)  
  63.         //            .HasMaxLength(40)  
  64.         //            .IsUnicode(false)  
  65.         //            .HasColumnName("stor_name");  
  66.   
  67.         //        entity.Property(e => e.Zip)  
  68.         //            .HasMaxLength(5)  
  69.         //            .IsUnicode(false)  
  70.         //            .HasColumnName("zip")  
  71.         //            .IsFixedLength(true);  
  72.         //    });  
  73.   
  74.         //    OnModelCreatingPartial(modelBuilder);  
  75.         //}  
  76.   
  77.         //partial void OnModelCreatingPartial(ModelBuilder modelBuilder);  
  78.     }  

3. Add the controller
 
In Solution Explorer, right-click the Controllers folder > Add > New Scaffolded Item. Then, select MVC Controller with views, using Entity Framework > Add.
 
 
Complete the Add MVC Controller with Views, using Entity Framework dialog,
  • Model class - Store(MVCCallWebAPI.Models.DB)
  • Data context class - pubsContext (MVCCallWebAPI.Models.DB)
  • Views - Keep the default of each option checked
  • Controller name - Change the default StoresController to StoresMVCController
  • Select Add
 
Visual Studio creates,
  • A StroesMVC controller (Controllers/StoresMVCController.cs)
  • Razor view files for Create, Delete, Details, Edit, and Index pages (Views/StoresMVC/*.cshtml)
 
The automatic creation of these files is known as scaffolding.
 
Step 4. Run and Test the app
 
Before we run the app, modify the header of the file: Views/Shared/_layout.cshtml Views, shown below, change the controller as StoreMVC and the app name as MVC app:
  1. <header>    
  2.     <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">    
  3.         <div class="container">    
  4.             <a class="navbar-brand" asp-area="" asp-controller="StroeMVC" asp-action="Index">MVC app</a>    
  5.             <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"    
  6.                     aria-expanded="false" aria-label="Toggle navigation">    
  7.                 <span class="navbar-toggler-icon"></span>    
  8.             </button>    
  9.             <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">    
  10.                 <ul class="navbar-nav flex-grow-1">    
  11.                     <li class="nav-item">    
  12.                         <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>    
  13.                     </li>    
  14.                     <li class="nav-item">    
  15.                         <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>    
  16.                     </li>    
  17.                 </ul>    
  18.             </div>    
  19.         </div>    
  20.     </nav>    
  21. </header>     
Now, we run the app,
 
 
Click MVC app, we got the screen,
 
 
This is a MVC app that consumes the  database directly through entity framework.
 

Summary

 
The .NET Core MVC module for Database-First approach is very different from the MVC module:
  1. There is no Entity Data Model Wizard avalaible for .NET Core as .NET does;
  2. You have to run PMC command to do the Reverse engineering Entity model from database
  3. The created code is very clear, not a black box as .NET, i.e., Developer can control the created code much easier. 
 
Reference