Simple Code First with Entity Framework 4 - Magic Unicorn Feature CTP 4 (original) (raw)

Space Needle made of LEGOMicrosoft's been releasing a number of right-sized LEGO pieces lately. In case you missed it, Betas have been announced for:

More details to come on all this. Howver, on the tooling side, I did get a chance to talk to Damian Edwards, a developer working on some of this stuff and I put video up on Channel 9 yesterday.

There's lots of cool pieces that are packaged up with WebMatrix initially, but these pieces are interesting for pro developers as well.

Still, something's missing.

imageIn my mind, it's been too hard to talk to databases. I like LINQ to SQL and used it on the first NerdDinner version, but since EF4 sucks so much less is way better than earlier versions of EF, Jon and I updated NerdDinner to use EF. It was easy, but I would have liked to code first, and code only if I could.

Microsoft announced a new Entity Framework CTP today. It has the romantic and wonderful name "Microsoft ADO.NET Entity Framework Feature CTP4" which is lame. You can say "EF Feature CTP4" but I like "EF Magic Unicorn Edition" but that's just me. We're getting the tech better at Microsoft but still can't get naming right. Whadayagonnado? Still, it makes EF a pleasure.

It's got a lot of interesting features and choices, and while it's still a CTP, you should take a minute and check it out.

To get a more detailed version of this walkthrough plus downloadable sample code, check out the ADO team's excellent blog post.

Quick CRUD via a Code First Model

After you install it (it won't mess up your system if you do), go and create a new whatever project. For my little example, I'll make a new ASP.NET MVC Website. It works for me better than a console app to illustrate a point.

Add a reference to Microsoft.Data.Entity.CTP.dll.

image

Make a new class, maybe in the Models folder, and name it something like Book. Add some code like this. Notice it's just code. Nothing derives from anything.

public class Book
{
[Key]
public int ISBN { get; set; }
[Required]
public string Title { get; set; }
public DateTime FirstPublished { get; set; }
public bool IsFiction { get; set; }
}

Notice I've put [Key] and [Required] on this class, but if that bothers me, I could put these kinds of declarations in a more fluent way in my database context class, in OnModelCreating.

builder.Entity().HasKey(b => b.ISBN);
builder.Entity().Property(b => b.Title).IsRequired();

To access my data, Here's a SimpleBookCatalog...

public class SimpleBookCatalog : DbContext
{
public DbSet Books { get; set; }
}

Next, I'll make a new Controller, via right|click Add Controller. I'll make a BookController.

public class BookController : Controller
{
SimpleBookCatalog db = new SimpleBookCatalog();

public ActionResult Index()  
{  
    var books = db.Books;  
    return View(books);  
}  
...  

}

I'll right click on the Index method and make an Index view. Then I'll run my app.

image

No data. What if I look in my SQL Management Studio? I got a Database created for me with a convention-based name since I didn't supply one.

SQL Management Studio with an automatically named Database

If I specified a different connection string, that DB could be anywhere.

However, if use a different database provider, like say, a SQL 4 Compact Edition one, setting it as the default in my Application_Start:

Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");

Then when I run my app and look in my App_Data folder:

SQL Compact Edition Database file in App_Data

So I got a file based database without doing anything and I don't need SQL Server. (Yes, I can change the name, location, etc.) If I do nothing, I get a reasonable convention.

Next, I'll add two Create methods, one for a GET and one for a POST. In Create, I'll add my new book and save the changes:

public ActionResult Create()
{
return View();
}

[HttpPost]
public ActionResult Create(Book book)
{
db.Books.Add(book);
db.SaveChanges();
return RedirectToAction("Index");
}

I'll right click, Add View, and make a Create View. Run my app, look at the empty list, then click Create.

My Create Form

Click Create, and I'm redirected back to the Index page:

Book List

Back on the Index page, I can change the link to Details to use our primary key:

<%: Html.ActionLink("Details", "Details", new { id=item.ISBN })%> |

Create a Details View and add a Details method:

public ActionResult Details(int id)
{
var book = db.Books.Find(id);
return View(book);
}

See that Find method? That's there automatically. I can certainly use al the LINQy goodness as well, but as you can see, CRUD is simple. I can hook up Edit and Delete in a few minutes as well.

Here's the whole thing:

public class BooksController : Controller
{
SimpleBookCatalog db = new SimpleBookCatalog();

public ActionResult Index()  
{  
    var books = db.Books;  
    return View(books);  
}

// GET: /Books/Details/5  
public ActionResult Details(int id)  
{  
    var book = db.Books.Find(id);  
    return View(book);  
}

// GET: /Books/Create  
public ActionResult Create()  
{  
    return View();  
}

[HttpPost]  
public ActionResult Create(Book book)  
{  
    db.Books.Add(book);  
    db.SaveChanges();  
    return RedirectToAction("Index");  
} 

// GET: /Books/Edit/5  
public ActionResult Edit(int id)  
{   
    return View(db.Books.Find(id));  
}

// POST: /Books/Edit/5  
[HttpPost]  
public ActionResult Edit(int id, FormCollection collection)  
{  
    var book = db.Books.Find(id);  
    UpdateModel(book);  
    db.SaveChanges();  
    return RedirectToAction("Index");  
}

// GET: /Books/Delete/5  
public ActionResult Delete(int id)  
{  
    var book = db.Books.Find(id);  
    return View(book);  
}

// POST: /Books/Delete/5  
[HttpPost]  
public ActionResult Delete(int id, FormCollection collection)  
{  
    db.Books.Remove(db.Books.Find(id));  
    db.SaveChanges();  
    return RedirectToAction("Index");  
}  

}

So that's a nice simple controller that uses a model that was written in just code. The database and its schema was created for me. The DbContext is LINQable with stuff like Add, Find, and Remove all just there. Plus, it's all EF under the hood, so if you need more complex stuff, you can do it.

For example, here's a more complex Code First Model with Collections, and more attributes. I show some fluent wiring up of relationships later on, although there are conventions that can assign bi-directionality based on naming.

public class Book
{
[Key]
public int ISBN { get; set; }
[Required]
public string Title { get; set; }
[Required]
public DateTime FirstPublished { get; set; }
[Required]
public bool IsFiction { get; set; }

public virtual Publisher Publisher { get; set; }  
[RelatedTo(RelatedProperty="Author")]  
public virtual Author Author { get; set; }  

}

public class Person
{
[ScaffoldColumn(false)]
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Author : Person
{
[ScaffoldColumn(false)]
public int AuthorId { get; set; }

public virtual ICollection<Book> Books { get; set; }  

}

public class Publisher
{
[ScaffoldColumn(false)]
public int PublisherId { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }

public virtual ICollection<Book> Books { get; set; }  

}

public class SimpleBookCatalog : DbContext
{
public DbSet Books { get; set; }
public DbSet People { get; set; }
public DbSet Authors { get; set; }
public DbSet Publishers { get; set; }
}

Also, "Magic Unicorn EF" supports DataAnnotations (or validation via Fluent interfaces), so those [Required] and [StringLength] stuff from before? Those apply not only in JavaScript, but also at the Server-side and Database persistence layers.

image

You can make your own strategies for creating databases, based on what's going on with the model, if it's changed, etc. Here's some built-in examples. Yours can do whatever you like. Here the SimpleBookCatalog is the DbContext from before.

//This is the default strategy. It creates the DB only if it doesn't exist
Database.SetInitializer(new CreateDatabaseOnlyIfNotExists());
//Recreates the DB if the model changes but doesn't insert seed data.
Database.SetInitializer(new RecreateDatabaseIfModelChanges());
//Strategy for always recreating the DB every time the app is run.
Database.SetInitializer(new AlwaysRecreateDatabase());

Connection Strings for the SQL 4 CE provider are simpler (like, they are possible to memorize, which is amazing, considering how hard they are now). For example:

Here's some examples of fluent mappings and setting up relationships to give you an idea of the kinds of things you can do, while avoiding looking at any visual designers. It all depends on how clean you need your POCO (Plain Old CLR Objects) to be.

modelBuilder.Entity().HasKey(b => b.ISBN);
modelBuilder.Entity().HasRequired(b => b.Title);
modelBuilder.Entity().HasRequired(b => b.Author).WithMany(a => a.Books);
modelBuilder.Entity().Property(p => p.Name).MaxLength = 50;
modelBuilder.Entity().HasMany(a => a.Books).WithRequired();

These configurations can be batched up into a class that handles configuration for a specific entity so you can reuse them and more easily config like this.

builder.Configurations.Add(new BookConfiguration());

All this is a really basic example as a means of introduction and for my own learning, but so far I like it. It takes just a few minutes to get a lot done without much code. Since this is all Entity Framework, I can put an OData service on top of my model really quickly and then start consuming those services from iPhones or whatever.

It'll be interesting to take a sample like Nerd Dinner or MVC Music Store and change them to use Code First EF and the Razor View Engine and see if they are more readable and how many fewer lines of code they are.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About Newsletter

Hosting By
Hosted on Linux using .NET in an Azure App Service