- A dialog pops up with all the fields to fill-in
- On save, if everything validates, the book is saved in the database
- The new book is added to the list with AJAX (did I mention it's a web app?)
- Query performance (N+1 Select)/Query complexity
- Formatting logic
- Type conversion
var books = Books.All();
foreach(var book in books) {
// display the data by accessing it this way:
book.Title
book.PublishedDate.ToString("MM/dd/yyyy")
book.Author.FormattedName
book.Genre.Name
}
- It suffers from the N+1 Select problem because for each book it does a query to lazy load the author and another query to lazy load the Genre (technically that's N+2).
- It formats the date with a .NET format string.
- It formats the author name using the format logic built in to the Author class in the FormattedName property
Query performance/complexity
This solves the N+1 Select problem, and is generally good enough for a simple example. But when the query is more complicated using the ORM to generate the SQL can be troublesome. And it's worth pointing out that written this way, the SQL will return all the fields from all the rows it joined to and selected from, even if only a small subset is needed. This may or may not affect performance, but it will impact the way indexes are defined.
- Using SQL directly means there are no limits on what features of the database can be used. Plus, the query can be optimized however needed.
- Only the required data fields need to be selected, instead of all the fields. And data from more than one table can be returned in one select without fancy eager loading features.
Formatting Logic
- Put the formatting logic in the select statement of the SQL query (duplicates the logic on Author)
- Put the formatting logic in a property of the query result object (duplicates the logic on Author)
- Refactor Author and call it's method to format the name (awkward)
public class Author {
...
public string FormattedName { get { return FirstName + " " + MiddleName + " " + LastName; } }
}
public class Author {
...
public string FormattedName { get { return FormatName(FirstName, MiddleName, LastName); } }
public static string FormatName(string first, string middle, string last) {
return first + " " + middle + " " + last;
}
}
public class BookListResult {
...
public string FormattedName { get { return Author.FormatName(FirstName, MiddleName, LastName); } }
}
Type Conversion
The other issue that must be dealt with when using the Query Result approach, deals with the AJAX part of our scenario. Remember how we wanted to add the book to the top of the list after the add? Well our view that renders the list item is going to be typed to expect a BookListResult, which is what the query returns. However, after the Add, the code will have a Book instance, not a BookListResult. So this requires a way to convert a Book into a BookListResult. I usually do this by adding a constructor to BookListResult that accepts a Book, and that constructor then "dots through" the book collecting all the data it needs.
From a certain perspective, this can be viewed as duplicating the query logic because knowledge of what fields the QueryResult's data comes from appears in two places: once in terms of the physical SQL tables in the SQL query, and again in terms of the Active Record objects.
Yet somehow I still prefer the Custom Query approach to the eager loading approach... I just like to have that absolute control over the SQL query. The cost of the boilerplate code here is worth it to me if it means I can directly leverage the query features of my database (like row number, and full text, and CTEs and pivots, etc etc).
As in the last "Minor Issues" post (constructors and MVC controllers), I'd love to hear your thoughts or experiences with these patterns.