What is Entity Framework?
For those who never stumbled upon Entity Framework, it is Object-Relational Mapping (ORM) framework. Its main purpose is to provide an abstraction of a database, so you can use the database as if using normal in-memory data collections.
In practice, you still need to be mindful of what you do write your LINQ upon, especially time-wise. However, the simplicity of building a database-powered persistence is stunning and, in my opinion, worth investing your time required for learning this framework.
Having no previous experience with Entity Framework, I had to dig into its history myself. Initially, it was part of .Net Framework 3.5, released in 2008 but its first version had very bad opinion among professionals. The second release (2010, with .Net Framework 4.0) was aiming to improve this.
Those versions supported only the database-first approach (C# classes were generated from the database structure) and the designer view (database and code were generated with UI based tool). The third version, in 2011, added the code-first approach, where the framework was able to generate a database according to the C# class.
Next major change was brought by version 6 in 2013. The project has become open-source, with the repository on GitHub. It is current version released for .Net Framework.
Although version 7 was planned, it was renamed to Entity Framework Core and was released with version 1.0, along with .Net Core in 2016. While the concept is similar, it was rewritten to be flexible and multiplatform, to fit nicely with new .Net Core world. Since then, Entity Framework is part of .Net Core environment.
So today, there are two alternatives with Entity Framework, you could go either with version 6 or with version 2.0 Core. Both are maintained and production-ready.
When to use Entity Framework and when to use Entity Framework Core?
It might sound like a profound question, to go with the new or to stay with the good old and engineering is often about picking the best solution in given circumstances. This might seems like a daunting task but Microsoft in its documentation gives a very straightforward answer (see https://docs.microsoft.com/en-us/ef/efcore-and-ef6/choosing for more details). Use Entity Framework 6, because it is more mature and feature-rich framework except when:
- you need to target .Net Core applications (one big exception…)
- you are creating a new application and you do not need features that are missing in EF Core.
What are those features? The documentation, once again, provides us with a neat list of differences (see https://docs.microsoft.com/en-us/ef/efcore-and-ef6/features). Please note, that in spite of initial statement, EF Core has some features that standard EF does not have, as designed for testing In-Memory database.
Onward to Visual Studio!
Now that we have said few words about the history, let’s write some code. We need to open the editor and create ASP.NET Core Web Application (Web Application Model-View-Controller). This will create an empty project with MVC pattern along with a default web structure.
There is no need for adding any NuGet packages or additional dependencies because this project comes with special Microsoft.AspNetCore.All package, that will handle all necessary NuGets for us.
For this demo, we will overengineer standard contact view and display our contact information from the database instead of putting them in resources or view. First of all, we would like to get an instance of an implemented interface in our controller as you’d normally do. For the sake of the exercise, we would like to be able to store new contact and read internal and external contacts. Our target interface has three members.
public interface IContactProvider { void AddContact(Contact contact); IEnumerable<Contact> GetExternalContacts(); IEnumerable<Contact> GetInternalContacts(); }
Entity Framework is an ORM, so we need an object that we will map our database table into. First, we need to create a class that will represent a row in the table, our Contact.
public class Contact { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public bool ExternalAddress { get; set; } }
Next, to represent the database, we will create a class that will inherit from DbContext (see https://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext(v=vs.113).aspx). You will find it in Microsoft.EntityFrameworkCore namespace, but thanks to the bulk NuGet packet, IntelliSense can add it for us. In this class, we need to do two things. First of all, pass configuration into DbContext and secondly create the table (DbSet). Both points are equally easy.
public class EFCoreDBContext : DbContext { public EFCoreDBContext(DbContextOptions<EFCoreDBContext> options) : base(options) { } DbSet<Contact> Contacts { get; set; } }
We could stop there because we have got all necessary components to write to and read from the database, but that wouldn’t be overengineering yet. To fix that, we will create a generic abstract class that will represent a table and then we will make a specific contact-related class. Our base class should give an easy access to specific data from the whole database.
public abstract class BaseContext<Entity> where Entity : class { private readonly EFCoreDBContext dbContext; public BaseContext(EFCoreDBContext dbContext) { this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); this.Data = dbContext.Set<Entity>(); } protected void SaveChanges() => this.dbContext.SaveChanges(); protected DbSet<Entity> Data { get; } }
And now, for the implementation we will create ContactContext class, that will handle our business logic and implement previously shown interface, so we can nicely pass it into our controller.
public class ContactContext : BaseContext<Contact>, IContactProvider { public ContactContext(EFCoreDBContext dbContext) : base(dbContext) { } public void AddContact(Contact contact) { this.Data.Add(contact); this.SaveChanges(); } public IEnumerable<Contact> GetExternalContacts() => this.Data.Where(c => c.ExternalAddress); public IEnumerable<Contact> GetInternalContacts() => this.Data.Where(c => !c.ExternalAddress); }
Great! We are halfway through, now we need to tend to our MVC architecture. Once again, let’s start with the model.
public class HomeViewModel { public IEnumerable<DisplayContact> PublicContacts { get; set; } }
public class DisplayContact { public string Name { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; }
Next step is to modify the Contact view, using Razor syntax to display PublicContacts.
@model EFCore.Models.HomeViewModel; @{ ViewData["Title"] = "Contact"; } <h2>@ViewData["Title"]</h2> <h3>@ViewData["Message"]</h3> <address> @foreach (var contact in @Model.PublicContacts) { <strong>@contact.Name</strong> <a>@contact.Email</a><br /> } </address>
The controller is responsible for handling model and displaying the view. To achieve that, we need to retrieve the previously implemented interface, store it and use to populate the model. Please note transition between the model from the database and model used to populate the view.
public class HomeController : Controller { private readonly IContactProvider contactProvider; public HomeController(IContactProvider contactProvider) { this.contactProvider = contactProvider; } public IActionResult Contact() { this.ViewData["Message"] = "Your contact page."; var model = new HomeViewModel { PublicContacts = this.contactProvider .GetExternalContacts() .Select(c => new Data.DisplayContact { Name = c.Name, Email = c.Email }) }; return View(model); } public IActionResult Index() { return View(); } public IActionResult About() { ViewData["Message"] = "Your application description page."; return View(); } public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
We are almost there, we just need to connect the dots in Startup class. Methods from this class are called by the runtime and have special purpose. We need to modify ConfigureServices to do just that – to add our services to the configuration. First we will add our database context as a service (thanks to that, every time we need to create a class that represents a table (like ContactContext) its constructor that needs EFCoreDBContext will be called. Secondly, we need to register implementation of our IContactProvider interface.
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<EFCoreDBContext>(options => { options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection")); }); services.AddMvc(); services.AddScoped<IContactProvider, ContactContext>(); }
Please note, that connection string is read from appsettings.json configuration file.
"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=EFCoreDemo.Website.MainDb;Trusted_Connection=True;" },
Last but not least, we need to initialize our database. Normally, you would use migrations to do that, however, this is a broad topic and deserves a separate post, together with proper configuration mechanic. For now, we will initialize the database from code.
In Startup class, there is a method that configures HTTP request pipeline – Configure. If you add as last parameter EFCoreDBContext (the class that was registered as DbContext), MVC will be kind enough to pass us its instance, and then we can call the initialization.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, EFCoreDBContext context) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); context.Initialize(); }
internal void Initialize() { //this.Database.EnsureDeleted(); this.Database.EnsureCreated(); if (!this.Contacts.Any()) { this.Contacts.Add(new Contact { Name = "Customer Service", Email = "customerservice@efcoredemo.com", ExternalAddress = true }); this.Contacts.Add(new Contact { Name = "Sales", Email = "sales@efcoredemo.com", ExternalAddress = true }); this.Contacts.Add(new Contact { Name = "IT Support", Email = "itsupport@efcoredemo.com", ExternalAddress = false }); this.SaveChanges(); } }
While this solution should never be used anywhere near production it works well enough so we can play with Entity Framework of ASP.Net generally. We create the database if it is not created, check if our table is empty and if it is, we will populate it with some data, saving changes at the end. The commented code (which you should never leave in codebase, except here) deletes the database. It is especially useful if you change the model – without it (or different, more sophisticated way of handling database scheme modification) your application will crash. If it work, you will see the following contact view.
And we are done! We have managed to display contact list the hard way. Obviously, there is much more to learn, we still need to setup production-ready database, handle secrets on the server side, master migrations and try more advanced scenarios. I hope you will find the code that we had made useful and reading this article informative. Writing it really was!