COMPSCI 280 S2 2014 Enterprise Software Development Further exploring database operations in MVC Jim Warren, jim@cs.auckland.ac.nz 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 2 COMPSCI 280 Last lecture… We introduced Language Integrated Query (LINQ) using (EmployeesContext db = new EmployeesContext()) { var emp = from e in db.Employees where e.DateOfBirth.Year < 1975 select e; return View(emp.ToList()); } 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> 3 COMPSCI 280 Handout 03 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 employee role And I want to present the user with the meaningful fields from SELECT * FROM employee,role WHERE employee.IDnum=role.Idnum; 4 COMPSCI 280 Handout 03 Extending the model In your .cs file in the Models directory Add the new DbSet to the DbContext public class EmployeesContext : DbContext { public EmployeesContext() : base("MySqlConnection") { } } public DbSet<Employee> Employees { get; set; } public DbSet<EmpRole> Roles { get; set; } And add a class definition matching the new table [Table("role")] public class EmpRole { [Key] public int jobnum { get; set; } public int IDnum { get; set; } public string Role { get; set; } } 5 Fine so far… but what’s the class of the Join result? COMPSCI 280 Handout 03 The Join In HomeController.cs we can use a LINQ query var ourEmployees = Also perfectly good, and better use of from e in db.Employees the LINQ language, would be to from r in db.Roles replace the 2nd FROM and the WHERE where e.IDnum == r.IDnum with: orderby e.Surname join r in db.Roles on e.IDnum select new EmpforGrid { equals r.IDnum IDnum = e.IDnum, Surname = e.Surname, GivenNames = e.GivenNames, YearOfBirth = e.DateOfBirth.Year, Role = r.Role}; Back in the model, we need to define this new class 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;} } 6 COMPSCI 280 Handout 03 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 @{ 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"))) 7 COMPSCI 280 Handout 03 The result 8 COMPSCI 280 Handout 03 ‘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 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(); 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 Show the join, a count(*) in the View. Sinceand I was counting I only want the first (and only) item in the collection that was returned nativeSQLQuery = "SELECT * FROM employee,role WHERE employee.IDnum=role.IDnum;"; var empr = db.Database.SqlQuery<EmpforGrid>(nativeSQLQuery); return View(empr.ToList()); 9 Since I’ve typed this as EmpforGrid, I can use it as the Model to pass to my View for the WebGrid COMPSCI 280 Handout 03 The result 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 Easily fixed by updating the SQL: 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 nativeSQLQuery = "SELECT *,Year(employee.DateOfBirth) as YearOfBirth FROM employee,role WHERE employee.IDnum=role.IDnum;"; 10 COMPSCI 280 Handout 03 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 11 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 COMPSCI 280 Handout 03 status Where we want to go 12 COMPSCI 280 Handout 03 And when we click an Edit link… 13 COMPSCI 280 Handout 03 The UPDATE Native SQL version Given that I have a reference to an object emp of class Employee with updated values: Re-establish the database connection 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); ... } Then we just .ExecuteSqlCommand (as compared doing . SqlQuery) on the database context to tell MySQL to have at it 14 COMPSCI 280 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 Handout 03 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. 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) 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 15 work with relational data using domain-specific objects – http://msdn.microsoft.com/en-us/data/ef.aspx COMPSCI 280 Handout 03 Putting the update in the MVC context Making an Edit controller In HomeController.cs we need a new method to ‘catch’ the We’ll set up the invocation such invocation of the Edit screen that the IDnum of the selected record is passed to this handler 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) .Find looks up a record from the return HttpNotFound(); dbSet based on its primary key return View(emp); (IDnum was set up as such in } MySQL and then this was If we picked it off the WebGrid table } conveyed to the MVC application 16 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) COMPSCI 280 by the [Key] decorator in the Model Handout 03 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 The [HttpPost] decorator signals that this filled out / updated by the user is the one to receive a completed HTML form (the previous one could’ve been [HttpPost] annotated as [HttpGet]) public ActionResult Edit(Employee emp) { if (ModelState.IsValid) { using (EmployeesContext db = new EmployeesContext()) { db.Entry(emp).State = EntityState.Modified; db.SaveChanges(); } The Edit screen will post back a return RedirectToAction("Index"); reference to the whole updated } Employee object, emp. If it’s valid return View(emp); (more on that later!) then we save } the update to the database 17 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 COMPSCI 280 Handout 03 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 18 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! COMPSCI 280 Handout 03 Inspecting the HTML 19 All that Razor boiled down to a hyperlink calling /Home/Edit/2 (the Home controller, looking for the Edit method with a parameter value of 2) COMPSCI 280 Handout 03 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) Now… 20 FYI: a battery of LINQ examples: http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b 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 COMPSCI 280 Handout 03