At CB Richard Ellis,
Rich Alger and I wrote and maintain an internal ordering system that uses
NHibernate. It was our first experience with NHibernate, but it was a huge success. Much of our initial focus was to just get things working, not to optimize it, since NHibernate has a bit of a steep learning curve. Now that we have more experience with it, we’re revisiting some of our strategies to improve them. My approach is this:
- Set up a SQL Trace on the dev database
- Load up the dev deployment of the ASP.NET app
- Load a page I want to optimize
- Count the number of queries going to the database as a result of loading that page
- Optimize queries till I’m making the fewest db trips possible
Some pages were sending hundreds of queries! We had a lot of the
SELECT N+1 problem going on, and some of those were pretty easy to fix. In general, we are trying to improve our loading strategies, to eagerly load everything we know we’ll need the FIRST time around, and to avoid the use of lazy loading, since it requires additional trips to the database. Lazy loading is great in dynamic scenarios, but if you already know you’re going to need something, don’t wait. Get it the first time around.
Which brings me to my current point. Eager loading with NH versus EF.
Ayende recently asked
what EFv4 can do that NH can’t. In a
subsequent post he replies to the many responses he received. I have an ongoing discussion with him in the comments regarding eager fetching.
Suppose you had these tables:
User <1---*> User_x_Role <*---1> Roles <1---*> Role_x_Permission <*---1> Permission
(The reason for the associative tables is that I hate many-to-many relationships. They are evil).
These tables set up a basic security model. Certain operations and resources require that you have a specific permission. Permissions are assigned to roles, and roles are assigned to users. A user’s permissions, then, are inherited from the roles he is assigned to. Permissions are not directly assignable to users. You must use a Role.
These tables would result in a model like this:
| User | The User class |
| Role | The Role class |
| Permission | The Permission class |
| User.Roles | An IList<Role> that contains the Roles assigned to the User |
| Role.Permissions | An IList<Permission> that contains the permissions assigned to the Role |
| Role.Users | An IList<User> that contains all of the Users that are assigned this Role |
| Permission.Roles | An IList<Role> that contains all of the Roles are that are assigned this Permission |
Checking if a User has a Permission may look like:
someUser.Roles.Permissions.Contains(somePermission);
So when fetching a user from the database, we may want to eagerly fetch his roles, and the permissions of those roles. With EF, I can do this:
using (MyEntities context = new MyEntities())
{
int userID = 6;
var qUser =
from u in context.User.Include("Roles.Permissions")
where u.ID == userID
select u;
User user = qUser.FirstOrDefault();
// Roles is already populated
int numRoles = user.Roles.Count;
// As are the Permissions of each Role
int numPermissions = user.Roles.First().Permissions.Count;
}
Note the use of
ObjectQuery<T>.Include(string path). Note that it’s chainable. If you had some other related entities or collections of User that you wanted to eagerly fetch you could do this:
from u in context.User
.Include("Roles.Permissions")
.Include("Orders.OrderDetails")
.Include("Employer")
.Include("ContactInfo")
select u;
Basically, you have complete control over what data will be eagerly fetched, and you’ll get back the exact object graph you want, with all of the collections populated, and related entities loaded. If you think that’s impressive, fire up SQL Profiler and do a trace on your database. All of the data for all of those entities and collections is retrieved in
one query. It may be full of subselects, joins, unions, etc, but it’s
one query. One trip to the database, to issue one query, and you have all the data you want. Because of the ease of use of
ObjectQuery<T>Include(string path), it’s easy to make methods on your entity classes that give you a LOT of flexibility when it comes to getting the data you want:
public static IList<Affiliate> GetPage(int pageIndex, int pageSize,
string where, string orderBy, out int rawRecordCount, params string[] includes)
{
// Start with a basic ObjectQuery<T> that would just return ALL rows (no criteria or sorting)
ObjectQuery<Affiliate> query = Jam2DataLayer.Instance.Context.Affiliate;
// Apply the where-clause if they provided one.
if (!String.IsNullOrEmpty(where))
query = query.Where(where);
// Apply the orderby-clause if they provided one
if (!String.IsNullOrEmpty(orderBy))
query = query.OrderBy(orderBy);
else
// If no sort-by clause was provided, sort by the ID desc (show newest records first)
// (There has to be sorting applied for paging to work with Skip() and Take())
query = query.OrderByDescending(ent => ent.ID);
// Execute the query at this point, but only to retrieve a count of the records it returns.
// We want to know the number of records our query WOULD return, if it weren't paged.
rawRecordCount = query.Count();
// Set up any eager loading that was specified
foreach (string include in includes)
query = query.Include(include);
// Get results just for the page we're interested in.
query = query.Skip(pageIndex * pageSize).Take(pageSize);
// Execute the query, storing the results in a List<T>
List<Affiliate> list = query.ToList();
return list;
}
// Example usage: Get the first 10 active Affiliate objects, with their User entity eagerly loaded
int totalRecords;
IList<Affiliate> activeAffiliates = Affiliate.GetPage(0, 10, "it.Active == true", null, out totalRecords, "User");
Definitely a handy method to have on each of your entity classes (note: in an upcoming post, I’ll be showing my T4 templates that use the
SFS Plugin for Visual Studio 2008 to generate just such code for each of your entity classes).
Now, can this be done in NHibernate? Honestly, I don’t know yet. I’ve been trying. My first attempt went something like this:
public virtual IList<T> GetPage<T>(int pageIndex, int pageSize, params string[] eagerFetches)
{
ICriteria crit = CurrentSession.CreateCriteria<T>();
crit.SetFirstResult(pageIndex * pageSize);
if (pageSize > 0)
crit.SetMaxResults(pageSize);
// Now set up the eager fetching for whatever the caller specified
foreach(string entity in eagerFetches)
crit.SetFetchMode(entity, FetchMode.Eager);
return crit.List<T>();
}
That seemed to work!
GetPage<User>(0, 10, “Roles”) DID indeed fetch 10 users, with their Roles collections already populated. Then I tried “Roles.Permissions”.
No dice! The data that comes back is correct, but it issues 10 separate queries. It’s still lazy loading. And, when you start to include multiple collections for the root entity, as in
GetPage<User>(0, 10, “Roles”, “Orders”), you start getting Cartesian products, producing millions of rows sometimes. NH doesn’t sort it out.
This question on StackOverflow has an answer that quotes from the NHibernate In Action book: From “NHibernate in Action”, page 225: NHibernate currently limits you to fetching just one collection eagerly.
Perhaps that is still the case. I mentioned this on Ayende’s blog, suggesting that perhaps this is an area where EF has the features in tow, and NH is still lacking. He told me that FetchMode.Eager is the same as FetchMode.Join, which would be the reason for the cartesian products. He said to look at FetchMode.Subselect, and Future queries. Well FetchMode.Subselect can only be set in the mapping file, it can’t be set arbitrarily on an ICriteria (the NHibernate.FetchMode enum does not have a field named Subselect), so that’s out. I want to leave my mappings as lazy, and specify eager only when I want it, on a per query basis. As for the Future<T> queries, I haven’t been able to get them to do what I want either. I might be able to get individual lists back, that all contain the related entities I’m interested in, by delaying the query to the database, storing the queries in an IMultiCriteria, but that isn’t going to give me a connected object graph, just disparate lists. I’ll keep poking around with different ways to hopefully accomplish the same thing in NH. For now, hopefully the discussion stays alive in the comments on his blog. I’d love to find a way to do this in NH, as it’s a powerful feature. I have a feeling that even if we conjure something up, it’s not going to be the same as what EF does, issuing only a single query to the database. Time will tell.
5 Unforgivable Windows 8 RTM Problems
I work with a couple of committees at Microsoft for developing the future “story” of some of their key technologies. So I’ve been able to participate in some fun closed focus groups for things like ASP.NET, WCF, Windows and Visual Studio 2012. The gist of it is that the guts of Windows 8 are superb. It is extremely optimized, and the OS has a much lower memory footprint. This was the core of their focus when developing Windows 8, because they need it to run on tablets. That means battery life is paramount. In fact, Windows 8 will run better than Windows 7 on the exact same computer. That’s a first for MS, and it’s pretty incredible.
Sadly, that’s where the incredible ends. I’ve been using the final RTM bits for a bit now, and there are a few problems really need to be addressed SOON. Some you’ve heard, some you probably haven’t. Here’s my current gripe list:
1. Two Separate Browser Processes
When you launch your browser from the Desktop, it feels like every version of Windows you’ve been familiar with since 1995. When you launch it from the Start Screen (the Modern UI), it launches a separate parent process, which is the full screen “immersive” browser. The 2 parent processes are separate, and it creates many, many problems. For one, now you have 2 sets of tabs. So when you have that article you open, and want to get back to reading later, you have to try and guess which browser it’s in. You can never see the 2 browsers on the same screen at the same time. You can’t move tabs from one to the other. In fact, in the Modern UI, you can’t even pull tabs out of Chrome at all. So if you wanted to pull that YouTube video out and watch it on the 2nd monitor, while you continue to do something else on your primary monitor, have fun copying and pasting both urls, going to your desktop, logging in to both sites (again), and finally pulling the youtube tab out. And I hope you don’t trust your browser to properly restore your tabs, because it fails. Hard. You’ll be lucky if you can restore the tabs from the LAST browser you closed. And since most people don’t Alt + F4, you never really close the Start Screen browser. It just gets suspended. Also, have fun logging in to every site TWICE, once in each browser. Logged into Gmail on the desktop Chrome? Log in again on the Modern Chrome. Oh, and don’t unpin the Start Screen IE from your Start Screen, because you can’t get it back. Ever. If you add IE back, it will only launch in the Desktop (which honestly, is what you probably want anyway).
2. Full Screen Apps
Apps created using the WinRT, designed for the new Modern UI (launched from the Start Screen) launch as a full screen process. I’m sure we’ve all accidentally hit F11 in our browsers, when we’re really trying to hit F12 to pull up the dev tools. WTF? Why is my browser full screen? Oh, F11. Oops. Well get used to it. When you launch an app from the start screen, it’s full screen. No border. You can’t resize it, minimize it, close it, move it, or look at anything else at the same time. You can’t see what other apps are running at a glance, or see your battery life, check the time, or see any tray icons. If you’re on a 3G device, you won’t be able to see how many bars you have, or if you’re even connected. Sure, you can try to drag it and snap the window to the side, so you can try to look at 2 apps at once, and maybe that’s a neat gimmick for tablets, but this is the DESKTOP. And you can’t even decide how wide to make the snap-in sidebar. Oh, and you know the little Gmail chat windows? Go ahead, try to click the “pop out” link, I dare you. It opens the chat in a new tab. And since you can’t pull the tabs out, the chat will now be the only thing you can look at, fullscreen. You can’t even pull the tab out to dock it in the sidebar. Forcing people into a full screen window with no option for arranging their work environment is many, many, many steps back, and overall just a huge insult to desktop users.
3. Sharing
This is a quick one, but it’s still annoying. Open a website in your Start Screen browser. Pull up the “charms” menu (move your mouse to the top right of your screen), and click Share. You can share a link to that site via Facebook, Twitter, Email, whatever. Now go to your Desktop browser. Or really, any app on your Desktop, and click the same Share button. “Nothing can be shared from the desktop, EVARRRR”. Ok, maybe I added the EVARRRR, but that’s what it feels like. So why even show the button? Related to this gripe is the Settings menu item in the Charms menu. It does different things depending on if you’re on the Start Screen or the Desktop. Annoying. Like, you can’t access the Control Panel unless you go to the (oh so outdated…) Desktop first. But who needs the control panel! It’s not like I need to install IIS or anything…
4. Two Taskbars
When you’re on the Desktop, you have your trusty taskbar. Even if the Start Menu is absent, at least you have shortcuts on your taskbar, and you can tell which of them are running, and which haven’t been launched yet. You can access MRU and jump lists by right clicking on them. It’s easy to spawn a second process, close multiple windows, or run something as an administrator. Oh, but it WON’T show you any apps that you’ve launched in the Modern UI. Those have their OWN taskbar. Move your mouse to the top left of your screen, and then pull it straight down. Bam! The sidebar pops out from the left, and shows you the tasks running in your start screen. Oh, and go ahead, try to alt+tab. You’ll see a list of DESKTOP processes, but none of your smart screen processes. You know what would be more convenient than 2 taskbars? Win7 in a VM on a Win7 machine. Hey, then you’d have TWO start menus! Way better than zero of them.
5. Two Operating Systems
Windows 8 just has this over all split personality feel to it. I’ve been using it daily, for both work and play, and it doesn’t get easier. It doesn’t start to feel more natural. It just gets more frustrating every time I have to look around for a while before I can find that browser tab I left open, or discover that it’s gone, because only half my tabs were restored. It gets more frustrating when I try to get back to an app with alt+tab, don’t see it in the list, and then have to check my SECOND task bar (which has nothing in common with the first one). It’s frustrating that I can’t have 3 or 4 windows on my screen arranged in a way that optimizes my workflow. Apps have to be fullscreen, you can’t look at 2 at the same time (sorry, their stupid docking thing is so effing broken and worthless it doesn’t count), and you can’t arrange anything. It’s no joke. This is two operating systems in one, and it SUCKS. HARD.