Reputation: 11990
I have a project where I am using Entity Framework 6 and ASP.NET MVC 5.
I have a need to use lazy loading instead of eager. In other words, I want to be able to access a relation/child after the parent model have been retrieved without using .Include()
Here is my simple use case
public ActionResult Test(int id)
{
using(var conn = new MyAppContent())
{
var user = conn.User.Where(u => u.Id == id).First();
var capsule = new SomeWrapper(user)
return View(capsule);
}
}
This is my SomeWrapper
class in a nutshell. (I removed most of the code for the sake of simplicity.)
public SomeWrapper
{
public User MyUser { get; set; }
public SomeWrapper(User user)
{
MyUser = user;
}
// Of course this class does more than this.
}
Here is my User
Model
public User
{
public int Id { get; set; }
public string Username { get; set; }
[ForeignKey("TimeZone")]
public int? TimeZoneId { get; set; }
}
Now I want to know the user's time zone from the view. So I did something like this
@model App.SomeWrapper
<p>Model.MyUser.Username<p>
<p>Model.MyUser.TimeZone.Name<p>
However, when trying to access the TimeZone.Name I get the following error
{"The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."}
I am not sure how it is disposed? I return View()
within my using(){}
block. so it should be disposed after I access it.
I can switch to eager loading "code below" to solve the problem, but I want to know how to do the same thing using lazy loading.
public ActionResult Test(int id)
{
using(var conn = new MyAppContent())
{
var user = conn.User.Where(u => u.Id == id).Include(x => x.TimeZone).First();
var capsule = new SomeWrapper(user)
return View(capsule);
}
}
How can I lazy load the TimeZone
relation correctly?
Upvotes: 1
Views: 533
Reputation: 239260
The problem is that your context should be request-scoped. By using using
in your action, it's scoped much less than that, and in particular is unavailable during the rendering of the view. Note that the fact that the return is inside is inconsequential; the view rendering can happen outside the action-scope.
Long and short, you just need to ensure you have a context that will survive the length of the request. The easiest way to do that is to inject it into your controller using a dependency injection container, and setting it to be request-scoped. If you don't want to go that far, simply make it an ivar on your controller (which by nature is request-scoped). You should also remember to override Dispose
on your controller, in that case, to dispose of the context properly:
private readonly MyAppContent conn = new MyAppContent();
// other controller code
protected override void Dispose(bool disposing)
{
if (disposing)
{
conn.Dispose();
}
base.Dispose(disposing);
}
Upvotes: 1
Reputation: 62260
You should not access MyUser.TimeZone
inside SomeWrapper
.
What you did back there is shallow copy User to MyUser, and mixing Domain Model (some call Entity Model) and View Model. It is not a good practice as Domain Model should never be exposed to public.
You should not shallow copy from Domain Model to View Model. Instead, you want deep copy using object-object mapper such as AutoMapper.
Entity Framework already provides lazy loading, but you cannot dispose DbContext right after retrieving data.
Instead, you'll need to inject MyAppContent using IoC container such as Autofac, Ninject.
For example,
public class MyController : Controller
{
private readonly MyAppContent _myAppContent;
public MyController(MyAppContent myAppContent)
{
_myAppContent = myAppContent;
}
public ActionResult Test(int id)
{
var user = _myAppContent.User.Where(u => u.Id == id).First();
...
}
}
Upvotes: 2