问题
I have an asp.net-mvc site and i am trying to understand the recommended practice around transactions and lazy loading.
When doing research around implementating nhibernate second level cache, one rule of thumb is around wrapping everything in a transaction (see here and here). I am trying to reconcile this point with how lazy loading works because they seems to be at odds given deferred query execution.
Lets say i have a query that looks like this . .
I have two entities:
- Project
- Owner
Here is a query in a transaction
public CacheTestViewModel GetCacheTestViewModel()
{
var vm = new CacheTestViewModel();
var session = Repository.Session;
using (var tx = session.BeginTransaction())
{
vm.Projects = Repository.Session.Query<Project>().Cacheable()
tx.Commit();
}
return vm;
}
but in my view I have the following code:
<% foreach (var project in Model.Projects {%>
<% = project.Owner %>
<%} %>
so that queries the Owner object when required due to lazy loading but when reviewing nhibernate profiler, it appears that these queries are happening AFTER the transaction commit is done so wanted to see if that breaks the principle in the first place. It seems like this will always be an issue with any lazy loading (either Fetch().Select() or Fetch().Select().Batch()
What is the recommended practice here?
回答1:
You should create ViewModels that represent everything that the View needs to be rendered. You should not be issuing database queries from the view if at all possible. To summarize the link above here:
- It increase the time that the connection to the database have to be open. The recommendation is to keep that open only for the duration of the action, not throughout the lifetime of the request.
- It make it that much harder to understand what are the data requirements for a particular action is.
- When writing views, you shouldn't be bothered with thinking about persistence, or the number of queries that you views are generating.
- The views are often the most changeable parts in the application, and having the application issue queries from the views may result in significant changes to the way the application data access pattern between revisions.
- Most often, queries from the views result from lazy loading, Select N+1 or similar bad practices.
We strongly recommend that you'll avoid generating queries in the view, instead, perform all your queries in the action, and provide in memory access only to the view for them to render themselves.
(Emphasis on the last point mine).
The practical implications of this are that you should not be lazy loading anything in a view. So what should you do instead? This is where the ViewModel layer comes in. You should be fully populating a thin ViewModel with the information you need and then rendering the view with that.
Furthermore, ViewModels shouldn't even contain classes mapped with NHibernate (this appears to be what you're doing in your example).
With all this in mind I would change your current code:
public class CacheTestViewModel
{
public List<ProjectViewModel> Projects { get; set; }
}
public class ProjectViewModel
{
public string Owner { get; set; }
/* etc. */
}
.. And then your code to populate those ViewModels:
public CacheTestViewModel GetCacheTestViewModel()
{
var vm = new CacheTestViewModel();
var session = Repository.Session;
using (var tx = session.BeginTransaction())
{
var projects = Repository.Session.Query<Project>().Cacheable();
foreach (var project in project)
{
vm.Projects.Add(new ProjectViewModel { Owner = project.Owner.Name });
}
tx.Commit();
}
return vm;
}
Now you might be thinking "gee, that's a lot of code to map domain entities to ViewModels. I can see that getting out of hand." You'd be right about that. There's a library called AutoMapper that you can use to semi-automate your mappings.
回答2:
The recommended practice is to have a view model that is separate from your domain model, and to have one view model per view. For a Projects view, there would be a corresponding ProjectsViewModel class that might look like:
public class ProjectsViewModel
{
public IList<ProjectViewModel> Projects { get; set; }
public class ProjectViewModel
{
public int ProjectId { get; set; }
public string Title { get; set; }
public string OwnerName { get; set; }
}
}
The ProjectsViewModel class would be fully populated in the transaction scope so that no lazy loading is necessary.
来源:https://stackoverflow.com/questions/22939302/doesnt-nhibernates-suggestion-of-putting-everything-in-a-transaction-work-again