Using Entity Framework in an ASP.NET MVC5 Application

In my previous two posts, I built a C# console application using Entity Framework (EF) in Microsoft Visual Studio 2013, and investigated how to perform CRUD operations on EF.

Building an ASP.NET MVC5 application using EF can become a straightforward job in Visual Studio 2013. It is very similar to building a J2EE MVC application by using JPA in Netbeans 7. The steps can be summarized as three steps: 1) create a ASP.NET MVC project; 2) add M: reversed engineer an EF model(M) from database; 3) Add C with V: scaffolding MVC 5 Controller (C) with views (V) using EF.

1. Create a ASP.NET MVC Project

In Microsoft Visual Studio 2013, create a new project (Ctrl-Shift-N) called EmployeeMVC using template (Visual C#->ASP.NET Web Application) and also select a template ‘MVC‘ and add folders and core references for ‘MVC’.

What will happene?
Visual Studio will create a few folders (App_Data, App_Start, Content, Controllers, fonts, Models, Scripts, Views) and files (Global.asax, packages.config, Web.config, Project_Readme.html, Startup.cs), also download a list of js scripts in Scripts folder, create AccountViewModels.cs and IdentityModels.cs in Models folder, AccountController.cs and HomeController.cs in Controllers folder, a list of *.shtml in various view folders, site style sheets in Contents folder and a few config.cs in App_Start.

Press F5 to build and run the empty application now.

2. Add ADO.NET Entity Data Model

Following the steps to reversed engineer EF 5 model from a table in the database into ASP.NET MVC (for reversed engineer an EF 6 model class, please use Entity Framework Power Tools):

  • Right click on Models folder, and choose New Item-Add->Data->ADO.NET Entity Data Model (C#);
  • Provide a Name: EmployeeModel.edmx and Click ‘Add’;
  • Choose ‘generate from database’ and Click ‘Next’
  • Connect to a SQL instance (local), choose your database (test) and table (HR.Employee)
  • If you receive a security warning, select OK to continue running the template.
  • This will add EmployeeModel.edmx (EF 5) as a folder under Models, and also add relevant references.

    Build the solution.

    3. Generate Controller and Views

    To generate controller and views:

  • Add the new controller to the existing Controllers folder. Right-click the Controllers folder, and select Add – New Scaffolded Item; Select the MVC 5 Controller with views, using Entity Framework option. This option will generate the controller and views for updating, deleting, creating and displaying the data in your model.
  • Set the controller name to EmployeeController, select Employee (EmployeeMVC.Models) for the model class, and select the testEntities (EmployeeMVC.Models) for the context class, Click ‘Add’
  • This will create EmployeeController.cs in Controllers:

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Entity;
    using System.Linq;
    using System.Net;
    using System.Web;
    using System.Web.Mvc;
    using EmployeeMVC.Models;
    
    namespace EmployeeMVC.Controllers
    {
        public class EmployeeController : Controller
        {
            private testEntities db = new testEntities();
    
            // GET: /Employee/
            public ActionResult Index()
            {
                var employees = db.Employees.Include(e => e.Employee1);
                return View(employees.ToList());
            }
    
            // GET: /Employee/Details/5
            public ActionResult Details(int? id)
            {
                if (id == null)
                {
                    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
                }
                Employee employee = db.Employees.Find(id);
                if (employee == null)
                {
                    return HttpNotFound();
                }
                return View(employee);
            }
    
            // GET: /Employee/Create
            public ActionResult Create()
            {
                ViewBag.mgrid = new SelectList(db.Employees, "empid", "lastname");
                return View();
            }
    
            // POST: /Employee/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="empid,lastname,firstname,title,titleofcourtesy,birthdate,hiredate,address,city,region,postalcode,country,phone,mgrid")] Employee employee)
            {
                if (ModelState.IsValid)
                {
                    db.Employees.Add(employee);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
    
                ViewBag.mgrid = new SelectList(db.Employees, "empid", "lastname", employee.mgrid);
                return View(employee);
            }
    
            // GET: /Employee/Edit/5
            public ActionResult Edit(int? id)
            {
                if (id == null)
                {
                    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
                }
                Employee employee = db.Employees.Find(id);
                if (employee == null)
                {
                    return HttpNotFound();
                }
                ViewBag.mgrid = new SelectList(db.Employees, "empid", "lastname", employee.mgrid);
                return View(employee);
            }
    
            // POST: /Employee/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="empid,lastname,firstname,title,titleofcourtesy,birthdate,hiredate,address,city,region,postalcode,country,phone,mgrid")] Employee employee)
            {
                if (ModelState.IsValid)
                {
                    db.Entry(employee).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                ViewBag.mgrid = new SelectList(db.Employees, "empid", "lastname", employee.mgrid);
                return View(employee);
            }
    
            // GET: /Employee/Delete/5
            public ActionResult Delete(int? id)
            {
                if (id == null)
                {
                    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
                }
                Employee employee = db.Employees.Find(id);
                if (employee == null)
                {
                    return HttpNotFound();
                }
                return View(employee);
            }
    
            // POST: /Employee/Delete/5
            [HttpPost, ActionName("Delete")]
            [ValidateAntiForgeryToken]
            public ActionResult DeleteConfirmed(int id)
            {
                Employee employee = db.Employees.Find(id);
                db.Employees.Remove(employee);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
    
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    db.Dispose();
                }
                base.Dispose(disposing);
            }
        }
    }
    
    

    It will also create a folder ‘Employee‘ in Views with a list of cshtml (Create, Delete, Details, Edit, Index)

    Build and run by pressing F5, and go to the similar link in the browser like:
    http://localhost:49481/employee

    I removed some of unwanted items in index.cshtml. My index page looked like:

    ASP.NET MVC 5 using EF

    ASP.NET MVC 5 using EF

    Summary

    Using Entity Framework, Visual Studio 2013 can easily generate a model from database, and scaffold a controller and generate all the views based on default templates and standard CRUD operations on EF.

    The advantage of this process is that the model, controller and view can be easily re-generated by repeating the above step 2 and 3 if the table structure in the underlying physical table is changed.

    The disadvantage is also obvious. Whatever customization made after the generation on (M. V. C) will have to be repeated after re-generation.

    T-SQL: OVER clause

    OVER clause defines a window or user-specified set of rows within a query result set. A window function then computes a value for each row in the window. You can use the OVER clause with functions to compute aggregated values such as moving averages, cumulative aggregates, running totals, or a top N per group results.

    The following is the script to show the usage of OVER clause (more in my T-SQL Script List).

    -- Step 1: Open a new query window to the TSQL2012 database
    USE TSQL2012;
    GO
    
    -- Step 2: Creating Views for OVER clause
    
    -- Setup views for demo
    IF OBJECT_ID('Production.CategorizedProducts','V') IS NOT NULL DROP VIEW Production.CategorizedProducts
    GO
    CREATE VIEW Production.CategorizedProducts
    AS
        SELECT  Production.Categories.categoryid AS CatID,
    			Production.Categories.categoryname AS CatName,
                Production.Products.productname AS ProdName,
                Production.Products.unitprice AS UnitPrice
        FROM    Production.Categories
                INNER JOIN Production.Products ON Production.Categories.categoryid=Production.Products.categoryid;
    GO
    IF OBJECT_ID('Sales.CategoryQtyYear','V') IS NOT NULL DROP VIEW Sales.CategoryQtyYear
    GO
    CREATE VIEW Sales.CategoryQtyYear
    AS
    SELECT  c.categoryname AS Category,
            SUM(od.qty) AS Qty,
            YEAR(o.orderdate) AS Orderyear
    FROM    Production.Categories AS c
            INNER JOIN Production.Products AS p ON c.categoryid=p.categoryid
            INNER JOIN Sales.OrderDetails AS od ON p.productid=od.productid
            INNER JOIN Sales.Orders AS o ON od.orderid=o.orderid
    GROUP BY c.categoryname, YEAR(o.orderdate);
    GO
    
    -- Step 3: Using OVER with ordering
    -- Rank products by price from high to low
    SELECT CatID, CatName, ProdName, UnitPrice,
    	RANK() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Production.CategorizedProducts
    ORDER BY PriceRank;
    
    -- Rank products by price in descending order in each category.
    -- Note the ties.
    SELECT CatID, CatName, ProdName, UnitPrice,
    	RANK() OVER(PARTITION BY CatID ORDER BY UnitPrice DESC) AS PriceRank
    FROM Production.CategorizedProducts
    ORDER BY CatID;
    
    -- Step 4: Use framing to create running total
    -- Display a running total of quantity per product category.
    -- This uses framing to set boundaries at the start
    -- of the set and the current row, for each partition
    SELECT Category, Qty, Orderyear,
    	SUM(Qty) OVER (
    		PARTITION BY category
    		ORDER BY orderyear
    		ROWS BETWEEN UNBOUNDED PRECEDING
    		AND CURRENT ROW) AS RunningQty
    FROM Sales.CategoryQtyYear;
    
    -- Display a running total of quantity per year.
    SELECT Category, Qty, Orderyear,
    	SUM(Qty) OVER (
    		PARTITION BY orderyear
    		ORDER BY Category
    		ROWS BETWEEN UNBOUNDED PRECEDING
    		AND CURRENT ROW) AS RunningQty
    FROM Sales.CategoryQtyYear;
    
    -- Show both side-by-side per category and per-year
    
    SELECT Category, Qty, Orderyear,
    	SUM(Qty) OVER (PARTITION BY orderyear ORDER BY Category	ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotalByYear,
    	SUM(Qty) OVER (PARTITION BY Category ORDER BY OrderYear	ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotalByCategory
    FROM Sales.CategoryQtyYear
    ORDER BY Orderyear, Category;
    
    -- Step 5: Clean up
    IF OBJECT_ID('Production.CategorizedProducts','V') IS NOT NULL DROP VIEW Production.CategorizedProducts
    IF OBJECT_ID('Sales.CategoryQtyYear','V') IS NOT NULL DROP VIEW Sales.CategoryQtyYear
    GO
    

    T-SQL: Common Table Expression

    A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement. A CTE is similar to a derived table in that it is not stored as an object and lasts only for the duration of the query. Unlike a derived table, a CTE can be self-referencing and can be referenced multiple times in the same query.

    A CTE can be used to create a recursive query, substitute a view etc. CTEs can be defined in user-defined routines, such as functions, stored procedures, triggers, or views.

    The following are some of testing examples. (see my T-SQL script List for other examples):

    -- Step 1: Open a new query window to the TSQL2012 database
    USE TSQL2012;
    GO
    -- Step 2: Common Table Expressions
    -- -- Select this query and execute it to show CTE Examples
    WITH CTE_year AS
    	(
    	SELECT YEAR(orderdate) AS orderyear, custid
    	FROM Sales.Orders
    	)
    SELECT orderyear, COUNT(DISTINCT custid) AS cust_count
    FROM CTE_year
    GROUP BY orderyear;
    
    -- Step 3 Recursive CTE 
    WITH EmpOrg_CTE AS
    (SELECT empid, mgrid, lastname, firstname --anchor query
    	FROM HR.Employees
    WHERE empid = 5 -- starting "top" of tree. Change this to show other root employees
    
    UNION ALL
    SELECT child.empid, child.mgrid, child.lastname, child.firstname -- recursive member which refers back to CTE
    	FROM EmpOrg_CTE AS parent
    	JOIN HR.Employees AS child
    	ON child.mgrid=parent.empid
    )
    SELECT empid, mgrid, lastname, firstname
    FROM EmpOrg_CTE;
    

    T-SQL: ISNULL, COALESCE, and NULLIF

    The following test demonstartes the usage of ISNULL, COALESCE, and NULLIF of T-SQL. For the complete list of my other tests and the creation scripts of TSQL2012 database, please refer to my T-SQL Script List.

    -- Step 1: Open a new query window to the TSQL2012 database
    USE TSQL2012;
    GO
    
    -- Step 2: Select and execute the following query to illustrate
    -- The ISNULL function
    SELECT custid, city, ISNULL(region, 'N/A') AS region, country
    FROM Sales.Customers;
    
    -- Step 3: Select and execute the following query to illustrate the
    -- COALESCE function
    SELECT custid, country, region, city,
    country + ',' + COALESCE(region, ' ') + ', ' + city as location
    FROM Sales.Customers;
    
    -- Step 4: Select and execute the following queries to illustrate the
    -- NULLIF function
    
    -- First, set up sample data
    CREATE TABLE dbo.employee_goals(emp_id INT , goal int, actual int);
    
    GO
    -- Populate the sample data
    INSERT INTO dbo.employee_goals
    VALUES(1,100, 110), (2,90, 90), (3,100, 90), (4,100, 80);
    
    -- Show the sample data
    SELECT emp_id, goal, actual
    FROM dbo.employee_goals;
    
    -- Use NULLIF to show which employees have actual
    -- values different from their goals
    SELECT emp_id, NULLIF(actual,goal) AS actual_if_different
    FROM dbo.employee_goals;
    
    -- Step 5: Clean up demo table
    DROP TABLE dbo.employee_goals;
    
    Design a site like this with WordPress.com
    Get started