Entity Framework: Database first or code first? Some non-conceptual, very practical differences in real life scenarios

In the last years, Microsoft has promoted Code First as a very comfortable way to make your web (or even client-server) application communicate with your database (I am not talking only about Sql Server. I have had good experience of Entity Framework with Oracle databases as well).

Code First, in contrast with Database first.

Database first is how it has always worked in the IT world:

  1. first you create a DB
  2. then you create objects in your application that are a representation of your DB, and modify the DB contents through the objects.

Code First works the other way around:

  1. first you create (business?) classes in your application
  2. then Entity framework creates the DB tables to hold those objects and keep track of the DB modifications.

There is a third approach (Model First), but I have never really given it a chance because the other two were really sufficient for what I do.

What is better? the Practical Approach

Let us see how the DB-classes link is created in Database First and how this changes in Code First.

The problem:

I am a tie salesperson. I have two entities that are linked:

  1. ties
  2. racks

A tie can be linked to one rack. Racks can hold many ties.

Managing Related Tables in Entity Framework Database First

These are my Racks

CREATE TABLE [dbo].[Rack] (
[Id] INT NOT NULL IDENTITY,
[RackPosition] NVARCHAR (MAX) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);

These are my Ties (linked to my Racks via the RackId, that is a foreign key)

CREATE TABLE [dbo].[Tie] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[TieSerial] INT NULL,
[RackId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);

ALTER TABLE [dbo].[Tie] WITH CHECK ADD CONSTRAINT [FK_Tie_Rack] FOREIGN KEY([RackId])
REFERENCES [dbo].[Rack] ([Id])
GO

These are the tables as you see them in Sql Management Studio:

Image of the two DB tables

The two tables created in the DB

In order to create the classes out of this Database, in Visual Studio we:

  1. Add or update the entity framework package to our web project (why not via NuGet, and why not 6.1, at the beginning of November 2014?)
  2. Add the ADO.NET Entity object to our project (we choose the option “EF Designer from DB”)
  3. We specify the connection string and finally import the DB objects

In Sql Management studio, we add some data to the Rack table, so that – when we create new ties – they can be hung on something!

Racks in the Rack table, Database first

Let us add some racks

Database first: choose what tables you want to import

DB Object to import in database first

We build the solution. At the end, the EF scripts create these class files we take good care of, because we will reuse them in Code First approach:

namespace DatabaseFirst
{
using System;
using System.Collections.Generic;

public partial class Tie
{
public int Id { get; set; }
public Nullable<int> TieSerial { get; set; }
public int RackId { get; set; }

public virtual Rack Rack { get; set; }
}
}

and

namespace DatabaseFirst
{
using System;
using System.Collections.Generic;

public partial class Rack
{
public Rack()
{
this.Ties = new HashSet<Tie>();
}

public int Id { get; set; }
public string RackPosition { get; set; }

public virtual ICollection<Tie> Ties { get; set; }
}
}

Please note: since the foreign key is on the Tie, this means a Tie has a Rack. A Rack has multiple Ties (thus, the ICollection of Ties) in the Rack object

Now, let us see what happens when we create an MVC controller and “scaffold” views

Let us create the views to edit these objects

MVC scaffolding of Database first objects

Below, the code we get for the Ties Controller. Note the bold statements: the scaffolding templates have recognized that, when we create or show a Tie, we also create or show the Rack it is bound to.

Note also that the templates makes use of the RackId field to create and modify the link between the Tie and the Rack.

public class TiesController : Controller
{
private DatabaseFirstDBEntities db = new DatabaseFirstDBEntities();

// GET: Ties
public ActionResult Index()
{
var ties = db.Ties.Include(t => t.Rack);
return View(ties.ToList());
}

// GET: Ties/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Tie tie = db.Ties.Find(id);
if (tie == null)
{
return HttpNotFound();
}
return View(tie);
}

// GET: Ties/Create
public ActionResult Create()
{
ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”);
return View();
}

// POST: Ties/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = “Id,TieSerial,RackId”)] Tie tie)
{
if (ModelState.IsValid)
{
db.Ties.Add(tie);
db.SaveChanges();
return RedirectToAction(“Index”);
}

ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”, tie.RackId);
return View(tie);
}

// GET: Ties/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Tie tie = db.Ties.Find(id);
if (tie == null)
{
return HttpNotFound();
}
ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”, tie.RackId);
return View(tie);
}

// POST: Ties/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = “Id,TieSerial,RackId”)] Tie tie)
{
if (ModelState.IsValid)
{
db.Entry(tie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction(“Index”);
}
ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”, tie.RackId);
return View(tie);
}

// GET: Ties/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Tie tie = db.Ties.Find(id);
if (tie == null)
{
return HttpNotFound();
}
return View(tie);
}

// POST: Ties/Delete/5
[HttpPost, ActionName(“Delete”)]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Tie tie = db.Ties.Find(id);
db.Ties.Remove(tie);
db.SaveChanges();
return RedirectToAction(“Index”);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}

The scaffolding creates not only the controller, but also the views.

This is the Create view (you will notice the creation of a dropdown list that allows us to choose the rack the tie is hung on)

DB First: creation of a new record

Creation of a new record with Entity Framework DB first

And last the index page, which shows what we just created

Tie Table in database first

The ties in our table

Noteworthy:

When the model is sent from the view to the controller, the rack object that is linked to the tie is null (see the breakpoint screenshot). However, the RackId key is not. This allows the DB to keep the link between the new tie and the chosen rack.

model of tie table entry

screenshot of the tie model

Managing Related Tables in Entity Framework Code First

To test how all of this works in the “Code First” world, I will do so:

Create a new Visual Studio project (web application, MVC)

  1. Upgrade EF to 6.1
  2. Prepare a new DB, called CodeFirst
  3. Create model classes from the same classes that were generated automatically by EF in Database First
  4. Add to the project an “Entity framework Code First” ADO.net object. This doesn’t do a lot: basically, it creates a new connection string for you [that you will have to change to make it point to your real DB].
  5. The ADO.net object also adds a DbContext class where you have to specify what classes will be written to the DB (this is another difference from Database First: naturally, Database first asks you where to read data from and what data it should read. Code First does not ask where it should write data and what it should write. You have to write additional code for that. But it’s not a lot.)

This is how the DbContext class looks like after our intervention. In bold, the code we added.

public class CodeFirstModel : DbContext
{
// Your context has been configured to use a ‘CodeFirstModel’ connection string from your application’s
// configuration file (App.config or Web.config). By default, this connection string targets the
// ‘CodeFirst.CodeFirstModel’ database on your LocalDb instance.
//
// If you wish to target a different database and/or database provider, modify the ‘CodeFirstModel’
// connection string in the application configuration file.
public CodeFirstModel()
: base(“name=DefaultConnection”)
{
}

// Add a DbSet for each entity type that you want to include in your model. For more information
// on configuring and using a Code First model, see http://go.microsoft.com/fwlink/?LinkId=390109.

// public virtual DbSet<MyEntity> MyEntities { get; set; }

public virtual DbSet<Tie> Ties { get; set; }
public virtual DbSet<Rack> Racks { get; set; }
}

Now, we ask the scaffolding engine to generate the controller exactly as we did with Database first

Code first in Entity framework: code creation

Code First Controller creation

The created controller is exactly like that of the Database first controller.

public class TiesController : Controller

{
private CodeFirstModel db = new CodeFirstModel();

// GET: Ties
public ActionResult Index()
{
var ties = db.Ties.Include(t => t.Rack);
return View(ties.ToList());
}

// GET: Ties/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Tie tie = db.Ties.Find(id);
if (tie == null)
{
return HttpNotFound();
}
return View(tie);
}

// GET: Ties/Create
public ActionResult Create()
{
ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”);
return View();
}

// POST: Ties/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = “Id,TieSerial,RackId”)] Tie tie)
{
if (ModelState.IsValid)
{
db.Ties.Add(tie);
db.SaveChanges();
return RedirectToAction(“Index”);
}

ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”, tie.RackId);
return View(tie);
}

// GET: Ties/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Tie tie = db.Ties.Find(id);
if (tie == null)
{
return HttpNotFound();
}
ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”, tie.RackId);
return View(tie);
}

// POST: Ties/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = “Id,TieSerial,RackId”)] Tie tie)
{
if (ModelState.IsValid)
{
db.Entry(tie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction(“Index”);
}
ViewBag.RackId = new SelectList(db.Racks, “Id”, “RackPosition”, tie.RackId);
return View(tie);
}

// GET: Ties/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Tie tie = db.Ties.Find(id);
if (tie == null)
{
return HttpNotFound();
}
return View(tie);
}

// POST: Ties/Delete/5
[HttpPost, ActionName(“Delete”)]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Tie tie = db.Ties.Find(id);
db.Ties.Remove(tie);
db.SaveChanges();
return RedirectToAction(“Index”);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}

The database is not created, yet.

To create it, either you enable Migrations (in Package console), or – more simply – you launch the application and, via the automatically generated views, create a DB entry that does not depend on other objects (you might as well seed some objects in the database via code, but we want to keep this example as simple as possible),

So, we create new Racks with a Racks controller (we do not start creating Ties because you cannot have a Tie without a Rack). The DB is automatically created. After we create some racks, we can add ties to them.

filling db items in code first

Create racks in Code first

Db created by code first approach

Codefirst database

How does the “automatic” DB look like? Well, it does look identical to the DB first approach: entity framework has indeed created two tables (with a plural name, but there is an option to specify it should remain singular) and the foreign keys.

Now we create our Ties

Tie table creation

We create Ties in CodeFirst now

We have obtained exactly the same result as with Database First.

Bottom line: what is better?

Rarely as in this occasion have I felt entitled to say: it’s the same, it depends on your inclinations and what you have at hand.

Database First is better if you have large databases you need your code to mirror (for smaller DBs, there is the opportunity to do Codefirst importing a part of the DB and creating the rest via .net classes)

Code First is better if you have an empty DB and don’t want to spend too much time switching between two different environments (the code IDE and the DB IDE).

With Code First, you have to learn some new attributes that you will use in your code to specify DB-related attributes as: a certain column has an index, a certain column should not go to the DB at all, and so on.

What do I prefer?

Lately I have gone with Code First because I have always been repaid by investments in technologies that automate certain processes even if at the beginning they seem to confront problems in a simplistic way.

Usually, these technologies improve and take away a lot of programming hassles. Take ORMs: how many developers would have bet they would almost totally replace field-level programming one day? Code First  gives you a more centralized view of your application, with potentially fewer bug to take care of in the future. That did the trick for me.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s