further exploring database operations in mvc jim warren, [email protected] compsci 280 s2 2014...
TRANSCRIPT
Further exploring database operations in MVCJim Warren, [email protected]
COMPSCI 280 S2 2014Enterprise Software Development
Today’s learning objectives To be able to query through a variety of
approaches including sending SQL directly to the DBMS and via LINQ
To be able to write more advanced queries in the MVC context, including joining tables and mapping the result to the View
To be able to update the database from the MVC context
COMPSCI 2802
Last lecture… We introduced Language Integrated Query (LINQ)
In this case the return value is a collection of objects of a class we’ve already defined in the Model Because we had said: public DbSet<Employee> Employees { get; set; }
and had defined the Employee class with property names exactly aligned to the columns of the DBMS table
And in the VIEW we had said:@model IEnumerable<MvcApplication1.Models.Employee>
Handout 03COMPSCI 2803
using (EmployeesContext db = new EmployeesContext()) {
var emp = from e in db.Employees where e.DateOfBirth.Year < 1975 select e;
return View(emp.ToList()); }
Dealing with a Join But what if we want to present the user with the
result of a Join on two (or more) tables? Say that there’s a ‘role’ table which maps ‘employee’
rows by their primary key IDnum to one or more job roles
And I want to present the user with the meaningful fields from
SELECT * FROM employee,role WHERE employee.IDnum=role.Idnum;Handout 03COMPSCI 2804
employee role
Extending the model In your .cs file in the Models directory
Add the new DbSet to the DbContext
And add a class definition matching the new table
Fine so far… but what’s the class of the Join result?Handout 03COMPSCI 2805
public class EmployeesContext : DbContext {public EmployeesContext() : base("MySqlConnection") { }
public DbSet<Employee> Employees { get; set; } public DbSet<EmpRole> Roles { get; set; }}
[Table("role")] public class EmpRole { [Key] public int jobnum { get; set; } public int IDnum { get; set; } public string Role { get; set; } }
The Join In HomeController.cs we can use a LINQ query
Back in the model, we need to define this new class
Handout 03COMPSCI 2806
var ourEmployees =from e in db.Employees
from r in db.Roles where e.IDnum == r.IDnum orderby e.Surname select new EmpforGrid { IDnum = e.IDnum, Surname = e.Surname, GivenNames = e.GivenNames, YearOfBirth = e.DateOfBirth.Year, Role = r.Role};
public class EmpforGrid { public int IDnum {get; set;} public string Surname {get; set;} public string GivenNames {get; set;} public int YearOfBirth { get; set;} public string Role { get; set;} }
Also perfectly good, and better use of the LINQ language, would be to replace the 2nd FROM and the WHERE with:join r in db.Roles on e.IDnum equals r.IDnum
The View In Index.cshtml (under Views/Home)
We define the model for the view as an enumerable collection of instances of the new class:@model IEnumerable<MvcLetsConnect.Models.EmpforGrid>
And we define the WebGrid itself
Handout 03COMPSCI 2807
@{ ViewBag.Title = "People"; WebGrid grid = new WebGrid(Model);}<h2>People</h2>@grid.GetHtml(columns: grid.Columns( grid.Column("IDnum","Employee ID"), grid.Column("Surname","Last Name"), grid.Column("GivenNames","Given Names"), grid.Column("YearOfBirth","Birth Year"), grid.Column("Role","Role")))
The result
Handout 03COMPSCI 2808
‘Native’ SQL You can also send ‘native’ SQL direct to the DBMS
Create a string, not interpreted as a query by VS And send it to the database context with the SqlQuery
method
Show the join, and a count(*)
Handout 03COMPSCI 2809
string the_name = "Tom";nativeSQLQuery = String.Format("SELECT COUNT(*) FROM employee WHERE GivenNames='{0}'",the_name);var cnt = db.Database.SqlQuery<int>(nativeSQLQuery);
ViewBag.empcnt = "Count=" + cnt.First();
nativeSQLQuery = "SELECT * FROM employee,role WHERE employee.IDnum=role.IDnum;";var empr = db.Database.SqlQuery<EmpforGrid>(nativeSQLQuery);
return View(empr.ToList());
The SqlQuery returns a ‘generic’ class that takes a type parameter (I’m counting here, so int is good)
I can just put anything in ViewBag and it’ll be available in the View. Since I was counting I only want the first (and only) item in the collection that was returned
Since I’ve typed this as EmpforGrid, I can use it as the Model to pass to my View for the WebGrid
The result
Easily fixed by updating the SQL:nativeSQLQuery = "SELECT *,Year(employee.DateOfBirth) as YearOfBirth FROM employee,role WHERE employee.IDnum=role.IDnum;";
Handout 03COMPSCI 28010
Oh, oops! EmpforGrid has a YearOfBirth not a DateofBirth (as in the employee table). It forgave me for leaving it out of the SqlQuery result and put in a ‘convenient’ default
Note I’m using the SQL syntax Year() for the conversion, not the C# syntax which is to invoke the .Year method on the DateTime object
What about the rest of the CRUD?! CRUD
Create, Read, Update, Delete A CRUD matrix can be a useful specification for
the scope of programming tasks E.g. to describe the ‘life cycle’ of each entity in a
system; e.g. a hotel reservation On some screen (e.g. ‘booking’) it is created (SQL INSERT,
not CREATE like making a new table) There may be a screen to update it (e.g. ‘change booking’)
which probably also reads the current value (‘R’ of CRUD, SQL SELECT)
And there will be one or more ways to delete it (e.g. from a cancellation action on the change booking screen or a dedicated cancellation screen, and also once the person has checked in) – then again, you might not actually delete in a SQL sense, but UPDATE it to cancelled or utilised status
Handout 03COMPSCI 28011
Where we want to go
Handout 03COMPSCI 28012
And when we click an Edit link…
Handout 03COMPSCI 28013
The UPDATE Native SQL version
Given that I have a reference to an object emp of class Employee with updated values:
Handout 03COMPSCI 28014
using (EmployeesContext db = new EmployeesContext()) { string sql=String.Format( "UPDATE employee SET Surname='{0}',GivenNames='{1}',"+ "DateOfBirth='{2:yyyy-MM-dd}' WHERE IDnum={3}", emp.Surname,emp.GivenNames,emp.DateOfBirth,emp.IDnum);
db.Database.ExecuteSqlCommand(sql);...}
Here we’re just building the text of a SQL command with the help of String.Format to plug in the parameters from our C# code at the curly braces (note the 3rd parameter - #2 counting from 0! – is formatted so MySQL recognises the date literal
Then we just .ExecuteSqlCommand (as compared doing . SqlQuery) on the database context to tell MySQL to have at it
Re-establish the database connection
The UPDATE v2 Entity Framework* version
Now assuming a bit more about emp… not just that it’s an object of class Employee, but that it’s already ‘attached’ to the database context.
As it turns out, this will be a correct assumption once we put this code into the appropriate part of the MVC application
*Entity Framework (EF) is an object-relational mapper that enables .NET developers to work with relational data using domain-specific objects –http://msdn.microsoft.com/en-us/data/ef.aspx Handout 03COMPSCI 28015
using (EmployeesContext db = new EmployeesContext()){ db.Entry(emp).State = EntityState.Modified; db.SaveChanges();...}
The 'State' property determines what's done by the SaveChanges method (modified entities are updated)
Putting the update in the MVC context Making an Edit controller
In HomeController.cs we need a new method to ‘catch’ the invocation of the Edit screen
Handout 03COMPSCI 28016
public ActionResult Edit(int id) {
ViewBag.Message = String.Format( "Your are editing the record for employee ID #{0}.",id);
using (EmployeesContext db = new EmployeesContext()) { Employee emp = db.Employees.Find(id); if (emp == null) return HttpNotFound(); return View(emp); } }
.Find looks up a record from the dbSet based on its primary key (IDnum was set up as such in MySQL and then this was conveyed to the MVC application by the [Key] decorator in the Model
If we picked it off the WebGrid table that we ourselves define it really should be found, but good to handle anyway (e.g. user might edit the URL string, or somebody might’ve deleted it since the user last refreshed their browser screen)
We’ll set up the invocation such that the IDnum of the selected record is passed to this handler
And we need a second version… In HomeController.cs we need a second signature
for the method This version catches the Edit screen when it’s being
returned to us filled out / updated by the user
Handout 03COMPSCI 28017
[HttpPost] public ActionResult Edit(Employee emp) { if (ModelState.IsValid) { using (EmployeesContext db = new EmployeesContext()) { db.Entry(emp).State = EntityState.Modified; db.SaveChanges(); } return RedirectToAction("Index"); } return View(emp); }
The Edit screen will post back a reference to the whole updated Employee object, emp. If it’s valid (more on that later!) then we save the update to the database
If the data was valid, the edit is done so redirect back to the home page; otherwise (there’s some invalid data on the form), present the form to the user again
The [HttpPost] decorator signals that this is the one to receive a completed HTML form (the previous one could’ve been annotated as [HttpGet])
Invoking the edit controller Putting an ActionLink into the WebGrid
To define the column with the Edit link on each employee:grid.Column(header: "Edit", format: (item) => Html.ActionLink("Edit", "Edit", new { id=item.IDnum }))
OK, a bit of cryptic Razor syntax here, but note We’re creating an ActionLink (labelled “Edit” and that
invokes the Edit handler in our controller) It’s per ‘item’ in the WebGrid (one link for each
employee) – ‘item’ is just a magic keyword to indicate the entity on the current row
The => is a ‘lambda expression’ (more on that later… it’s a function as a parameter)
We assign the .IDnum of the current item (i.e. it’s primary key) to ‘id’ – which is the parameter expected by our controller code!
Handout 03COMPSCI 28018
Inspectingthe HTML
All that Razor boiled down to a hyperlink calling /Home/Edit/2 (the Home controller, looking forthe Editmethod witha parametervalue of 2)
Handout 03COMPSCI 28019
Where we’re up to We’ve seen that we can interact with our DBMS
by sending it native SQL or using C#/.NET language-embedded syntax (LINQ and Entity Framework)
And we can add and invoke additional handlers in our controller (e.g. for an Edit/Update function) FYI: a battery of LINQ examples:
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
Now… Work the second labsheet (if you haven’t already) Get seriously into Assignment 2 Next lecture we’ll look at creating the HTML form to
get interactive input from the user for the Edit / Update
Handout 03COMPSCI 28020