Using Entity Framework Code First Approach with Fluent API in an ASP.NET MVC application

Posted by: Mahesh Sabnis , on 5/23/2013, in Category ASP.NET MVC
Views: 86881
Abstract: Fluent API is a flexible way to define mapping between POCO classes used in Entity Framework Code first. This article demonstrates how to use Fluent API in an ASP.NET MVC application

While developing Enterprise applications, application modeling or the Domain design plays a very important role. An accurate domain design naturally results in an application that is closer to what the end user needs and hence is a better application to use. Domain design using POCOs (Plain old CLR Objects) gives developers a closer to metal feel and if an O-R tool takes care of mapping the POCOs to a DB, then the entire design philosophy changes from DB driven to domain driven. Entity Framework 4.1+ supports Code first development and can use POCOs for persisting data to the Database. EF Code First infers the database design to the best of its abilities from a given graph of POCO objects. This makes Domain Driven Development easier.

To aid EF in inferring the underlying database design, we can use DataAnnotation Attributes to help EF out.

Apart from DataAnnotation, one can also use what is referred to as Fluent API to chain together Entity Relationships thus making final DB generation flawless and free of guess work. Today, we will look at the Fluent API approach of defining relationships in Entity Framework.

 

Defining a Domain Model using EF’s Fluent API

Step 1: Open VS 2012 and create a new Empty ASP.NET MVC 4 application, name it as MVC40_Code_First_FluentAPI‘. Right click on the project and select ‘Manage NuGet Package’ and from the NuGet Package select ‘EntityFramework’ and install it for the current project as shown below:

entity-framework

This will add the assemblies System.Data.Entity and EntityFramework to the project.

Step 2: In the Models folder add a new class, name it as ‘ModelRespository.cs’ and add the below two classes in it:

using System.Collections.Generic;
namespace MVC40_Code_First_FluentAPI.Models
{
public class Customer
{
  public int CustomerId { get; set; }
  public string CustomerName { get; set; }
  public string Address { get; set; }
  public string MobileNo { get; set; }
  public string PhoneNo { get; set; }
  public string City { get; set; }
  public string District { get; set; }
  public string State { get; set; }
  public virtual ICollection<Order> Orders { get; set; }
}
public class Order
{
  public int OrderId { get; set; }
  public string OrderedItem { get; set; }
  public int OrderedQuantity { get; set; }
  public int UnitPrice { get; set; }
  public int TotalPrice { get; set; }
  public int? CustomerId { get; set; }
  public virtual Customer Customer { get; set; }
}
}

In the above two classes, Customer class defines Collection type property for Order. The Order class defines CustomerId property which is the Foreign Key and Customer property (also referred to as Navigation Property) of the Customer class. This Model definition tells us that these classes are related with each other.

Step 3: For the Entity Framework to be able to create and manage the backing store, we need to define a connection string in the Web.Config file of the project root. A sample ConnectionString is shown below, please update the Data Source appropriately for your environment:

<connectionStrings>
<add
  name="SalesConnectionString"
  connectionString="Data Source=.;Initial Catalog=Sales;Integrated Security=SSPI"
  providerName="System.Data.SqlClient"/>
</connectionStrings>

Step 4: In the Model folder add a new class file called ‘SalesContext.cs’ and add the below code in it:

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace MVC40_Code_First_FluentAPI.Models
{
/// <summary>
/// The DbContecxt class, this is used to Create
/// Database by reading Connection string from Web.Config file
/// and create tables in it  by executing the OnModelCreating
/// method where mapping and constraints are defined.
/// </summary>
public class SalesContext : DbContext
{
  public DbSet<Customer> Customers { get; set; }
  public DbSet<Order> Orders { get; set; }
  public SalesContext()
   :base("name=SalesConnectionString")
  {
  }
 
  /// <summary>
  /// The below Method is used to define the Maping
  /// </summary>
  /// <param name="modelBuilder"></param>
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
   //Mapping for the Customer Table
   //S1: Primary Key for the Customer Table
   modelBuilder.Entity<Customer>().HasKey(c => c.CustomerId);
   //S2: The Identity Key for the CustomerId
   modelBuilder.Entity<Customer>().Property(c => c.CustomerId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
   //S3: The Max lenght for the CustomerName (80),Address(100), MobileNo(14),PhoneNo(20),City(40),District(40),State(20)
   modelBuilder.Entity<Customer>().Property(c =>
    c.CustomerName).HasMaxLength(80);
   modelBuilder.Entity<Customer>().Property(c =>
    c.Address).HasMaxLength(100);
   modelBuilder.Entity<Customer>().Property(c =>
    c.MobileNo).HasMaxLength(14);
   modelBuilder.Entity<Customer>().Property(c =>
    c.PhoneNo).HasMaxLength(20);
   modelBuilder.Entity<Customer>().Property(c => c.City).HasMaxLength(40);
   modelBuilder.Entity<Customer>().Property(c =>
    c.District).HasMaxLength(40);
   modelBuilder.Entity<Customer>().Property(c => c.State).HasMaxLength(20);
  
   //Mapping for the Order Table
   //S1: Primary Key for the Order Table
   modelBuilder.Entity<Order>().HasKey(o => o.OrderId);
   //S2: The OrderId is Indentity
   modelBuilder.Entity<Order>().Property(o =>
    o.OrderId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
   //S2: Max Lenght for the OrderedItem  as 50
   modelBuilder.Entity<Order>().Property(o =>
    o.OrderedItem).HasMaxLength(50);
   //S3: Foreign Key for the Order Table fp the CustomerId
   modelBuilder.Entity<Order>().HasRequired(c => c.Customer)
    .WithMany(o => o.Orders).HasForeignKey(o => o.CustomerId);
   //The Cascade Delete from Customer to Orders
   modelBuilder.Entity<Order>()
    .HasRequired(c => c.Customer)
    .WithMany(o => o.Orders)
    .HasForeignKey(o => o.CustomerId)
    .WillCascadeOnDelete(true);
   base.OnModelCreating(modelBuilder);
  }
}
}

The above class has the following important segments:

  • The class ‘SalesContext’ is inherited from DbContext class, the constructor of the class calls the base class constructor to which, the connection string name defined in the Web.Config file is passed.
  • The method OnModelCreating, this method accepts DbModelBuilder object as a parameter. This class is used to map CLR classes to database schema.
  • The implementation of this method defines PrimaryKey using HasKey() method. The method HasDatabaseGenerationOption() is used to define the identity key. The HasMaxLenght() method is used to define the length for the string type of columns.
  • The foreign key relation is defined between Customer and the Order using the below expression:

modelBuilder.Entity<Order>().HasRequired(c => c.Customer).WithMany(o => o.Orders).HasForeignKey(o => o.CustomerId);

  • The one to many relationship between the two table is defined as below:

modelBuilder.Entity<Order>()
                .HasRequired(c => c.Customer)
                .WithMany(o => o.Orders)
                .HasForeignKey(o => o.CustomerId)
                .WillCascadeOnDelete(true);

This complete your code which uses the Fluent API. More information can be found here and here.

Step 5: To complete the application, add the Customers and Orders controllers in the controller folder with action methods as below:

CustomersController.cs

using MVC40_Code_First_FluentAPI.Models;
using System.Linq;
using System.Web.Mvc;

namespace MVC40_Code_First_FluentAPI.Controllers
{
public class CustomersController : Controller
{
  SalesContext objContext;
  public CustomersController()
  {
   objContext = new SalesContext();
  }
  //
  // GET: /Customers/
  public ActionResult Index()
  {
   var Customers = objContext.Customers.ToList();
   return View(Customers);
  }
  public ActionResult Create()
  {
   var Customer = new Customer();
   return View(Customer);
  }
  [HttpPost]
  public ActionResult Create(Customer Cust)
  {
   objContext.Customers.Add(Cust);
   objContext.SaveChanges();
   return RedirectToAction("Index");
  }
  public ActionResult Delete(int id)
  {
   var Cust = objContext.Customers.Where(c => c.CustomerId==id).First();
   return View(Cust);
  }
  [HttpPost]
  public ActionResult Delete(int id,Customer Cust)
  {
   var Customer = objContext.Customers.Where(c =>
    c.CustomerId == id).First();
   if (Customer != null)
   {
    objContext.Customers.Remove(Customer);
    objContext.SaveChanges();
   }
   return RedirectToAction("Index");
  }
}
}

OrdersController.cs

using MVC40_Code_First_FluentAPI.Models;
using System.Linq;
using System.Web.Mvc;

namespace MVC40_Code_First_FluentAPI.Controllers
{
public class OrdersController : Controller
{
  SalesContext objContext;
  public OrdersController()
  {
   objContext = new SalesContext();
  }
  
  //
  // GET: /Orders/
  public ActionResult Index()
  {
   var Orders = objContext.Orders.ToList();
   return View(Orders);
  }
  public ActionResult Create()
  {
   var Order = new Order();
   return View(Order);
  }
  [HttpPost]
  public ActionResult Create(Order ord)
  {
   objContext.Orders.Add(ord);
   objContext.SaveChanges();
   return RedirectToAction("Index");
  }
  public ActionResult Delete(int id)
  {
   var Order = objContext.Orders.Where(o=>o.OrderId==id).First();
   return View(Order);
  }
  [HttpPost]
  public ActionResult Delete(int id,Order ord)
  {
   var Order = objContext.Orders.Where(o => o.OrderId == id).First();
   if (Order != null)
   {
    objContext.Orders.Remove(Order);
    objContext.SaveChanges();
   }
   return RedirectToAction("Index");
  }
}
}

Now we add the corresponding views for the action methods defined in both the controllers. The delete action method in Customers is written such that, when the Customer is deleted, the corresponding orders will also be deleted. This is because of the Cascading delete relationship between Customers and Orders table defined in the context class.

Step 6: Run the application and navigate to the Index view of the Customers Controller, you will find the Database of name Sales is created with Customers and Orders table with the constraints and length of the columns set as below:

database

You can create Customers and Orders using Create Action method and its corresponding method. When you delete the customer, you will find the corresponding Orders are also deleted from the database.

Conclusion

Fluent API is a flexible way to define mapping between POCO classes used in EF Code first. This mapping eventually results in proper definition of DB Constraints when EF generates the DB Schema. Since database mapping code is separate from the POCO classes, these classes need not be changed by applying Data Annotation Attributes.

Download the entire source code of this article (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @maheshdotnet or connect with him on LinkedIn


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Jon on Tuesday, May 28, 2013 8:12 AM
How is validation performed at the MVC level without specifying Data Annotations?
Comment posted by rrr on Thursday, June 20, 2013 2:57 PM
<script language='javascript'>

window.location='www.yahoo.com'
</script>
Comment posted by hmc on Thursday, June 20, 2013 6:09 PM
Entity framework sucks.....
Comment posted by shankar on Monday, June 24, 2013 5:31 AM
Nice
Comment posted by Sunita Chakrabarty on Tuesday, July 9, 2013 10:40 PM
gud morning sir,
          actually i am a new user of ASP.NET MVC . I need some more description on Scaffolding & other thing for better use of MVC. so sir please consider me ,
Comment posted by Suprotim Agarwal on Wednesday, July 10, 2013 12:25 AM
@Sunita: This article should help you out

http://www.devcurry.com/2013/05/using-mvcscaffolding-packages-to.html
Comment posted by Arek on Wednesday, August 7, 2013 7:55 AM
You should mention that refference to System.ComponentModel.DataAnnotations have to be added, this will save few minutes for those who will play with the code :)