There is a thing that's been bugging me for a long time about Entity Framework.
关于实体框架,有很长一段时间一直困扰着我。
Last year I wrote a big application for a client using EF. And during the development everything worked great.
去年,我为使用EF的客户编写了一个大型应用程序。在开发过程中,一切都很顺利。
We shipped the system in august. But after some weeks I started to see weird memory leaks on the production-server. My ASP.NET MVC 4 process was taking up all the resources of the machine after a couple of days running (8 GB). This was not good. I search around on the net and saw that you should surround all your EF queries and operations in a using()
block so that the context can be disposed.
我们八月发货了。但几周后我开始在生产服务器上看到奇怪的内存泄漏。我的ASP.NET MVC 4进程在运行几天(8 GB)后占用了机器的所有资源。这不好。我在网上搜索并看到你应该在using()块中包围所有EF查询和操作,以便可以处理上下文。
In a day I refactored all my code to use using()
and this solved my problems, since then the process sits on a steady memory usage.
在一天中,我重构了我使用()的所有代码,这解决了我的问题,因为这个过程依赖于稳定的内存使用。
The reason I didn't surround my queries in the first place however is that I started my first controllers and repositories from Microsofts own scaffolds included in Visual Studio, these did not surround it's queries with using, instead it had the DbContext
as an instance variable of the controller itself.
我首先没有围绕我的查询的原因是我从Visual Studio中包含的Microsofts自己的脚手架开始我的第一个控制器和存储库,这些没有使用包围它的查询,而是将DbContext作为实例变量控制器本身。
First of all: if it's really important to dispose of the context (something that wouldn't be weird, the dbconnection
needs to be closed and so on), Microsoft maybe should have this in all their examples!
首先:如果处理上下文非常重要(某些事情不会很奇怪,dbconnection需要关闭等等),微软可能应该在他们的所有示例中都有这个!
Now, I have started working on a new big project with all my learnings in the back of my head and I've been trying out the new features of .NET 4.5 and EF 6 async
and await
. EF 6.0 has all these async methods (e.g SaveChangesAsync
, ToListAsync
, and so on).
现在,我已经开始研究一个新的大项目了解我的所有知识,并且我一直在尝试.NET 4.5和EF 6 async的新功能等待。 EF 6.0具有所有这些异步方法(例如SaveChangesAsync,ToListAsync等)。
public Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
In class TblLanguageRepo
:
在课堂上TblLanguageRepo:
public async Task<tblLanguage> Add(OrganizationTypeEnum requestOrganizationTypeEnum, tblLanguage language)
{
...
await Context.SaveChangesAsync();
return langaugeDb;
}
However, when I now surround my statements in a using()
block I get the exception, DbContext was disposed
, before the query has been able to return. This is expected behaviour. The query runs async and the using
block is finished ahead of the query. But how should I dispose of my context in a proper way while using the async and await functions of ef 6??
但是,当我现在在using()块中包含我的语句时,我得到了异常,在查询能够返回之前,DbContext被处理掉了。这是预期的行为。查询运行async,使用块在查询之前完成。但是在使用ef 6的async和await函数时,我应该如何以适当的方式处理我的上下文?
Please point me in the right direction.
请指出我正确的方向。
Is using()
needed in EF 6? Why do Microsoft's own examples never feature that? How do you use async features and dispose of your context properly?
在EF 6中需要使用()吗?为什么微软自己的例子从未有过这样的特色?如何正确使用异步功能并处理上下文?
5 个解决方案
#1
24
Your code:
你的代码:
public Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
is disposing the repository before returning a Task
. If you make the code async
:
在返回任务之前处置存储库。如果您使代码异步:
public async Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return await langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
then it will dispose the repository just before the Task
completes. What actually happens is when you hit the await
, the method returns an incomplete Task
(note that the using
block is still "active" at this point). Then, when the langRepo.Add
task completes, the Post
method resumes executing and disposes the langRepo
. When the Post
method completes, the returned Task
is completed.
然后它将在任务完成之前处置存储库。实际发生的是当你点击等待时,该方法返回一个不完整的任务(注意此时使用块仍然是“活动的”)。然后,当langRepo.Add任务完成时,Post方法继续执行并释放langRepo。 Post方法完成后,返回的Task完成。
For more information, see my async
intro.
有关更多信息,请参阅我的异步介绍。
#2
4
I would go for the 'one DbContext per request' way, and reuse the DbContext within the request. As all tasks should be completed at the end of the request anyway, you can safely dispose it again.
我会选择“每个请求一个DbContext”方式,并在请求中重用DbContext。由于所有任务都应在请求结束时完成,您可以再次安全地处理它。
See i.e.: One DbContext per request in ASP.NET MVC (without IOC container)
参见:ASP.NET MVC中每个请求一个DbContext(没有IOC容器)
Some other advantages:
其他一些优点:
- some entities might already be materialized in the DbContext from previous queries, saving some extra queries.
- 某些实体可能已经在之前的查询中在DbContext中实现,从而节省了一些额外的查询。
- you don't have all those extra
using
statements cluttering your code. - 你没有那些额外的使用语句混乱你的代码。
#3
1
If you are using proper n-tiered programming patters, your controller should never even know that a database request is being made. That should all happen in your service layer.
如果您使用正确的n层编程模式,您的控制器甚至不应该知道正在进行数据库请求。这应该都发生在你的服务层。
There are a couple of ways to do this. One is to create 2 constructors per class, one that creates a context and one that accepts an already existing context. This way, you can pass the context around if you're already in the service layer, or create a new one if it's the controller/model calling the service layer.
有几种方法可以做到这一点。一种是为每个类创建2个构造函数,一个用于创建上下文,另一个用于接受已存在的上下文。这样,如果您已经在服务层中,则可以传递上下文,如果是调用服务层的控制器/模型,则可以创建新上下文。
The other is to create an internal overload of each method and accept the context there.
另一种是创建每个方法的内部重载并接受那里的上下文。
But, yes, you should be wrapping these in a using.
但是,是的,你应该将它们包装在一个使用中。
In theory, the garbage collection SHOULD clean these up without wrapping them, but I don't entirely trust the GC.
理论上,垃圾收集应该清理它们而不包装它们,但我并不完全信任GC。
#4
1
I agree with @Dirk Boer that the best way to manage DbContext lifetime is with an IoC container that disposes of the context when the http request completes. However if that is not an option, you could also do something like this:
我同意@Dirk Boer的说法,管理DbContext生命周期的最佳方法是使用一个IoC容器,在http请求完成时处理上下文。但是,如果这不是一个选项,你也可以这样做:
var dbContext = new MyDbContext();
var results = await dbContext.Set<MyEntity>.ToArrayAsync();
dbContext.Dispose();
The using
statement is just syntactic sugar for disposing of an object at the end of a code block. You can achieve the same effect without a using
block by simply calling .Dispose
yourself.
using语句只是用于在代码块末尾处理对象的语法糖。只需调用.Dispose,就可以在没有使用块的情况下实现相同的效果。
Come to think of it, you shouldn't get object disposed exceptions if you use the await
keyword within the using block:
想想看,如果在using块中使用await关键字,则不应该获取对象处置异常:
public async Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
var returnValue = langRepo.Add(RequestOrganizationTypeEnum, language);
await langRepo.SaveChangesAsync();
return returnValue;
}
}
#5
0
IMHO, it's again an issue caused by usage of lazy-loading. After you disposed your context, you can't lazy-load a property anymore because disposing the context closes the underlying connection to the database server.
恕我直言,这又是一个由懒惰加载引起的问题。在处理完上下文后,您不能再延迟加载属性,因为处理上下文会关闭与数据库服务器的基础连接。
If you do have lazy-loading activated and the exception occurs after the using
scope, then please see https://*.com/a/21406579/870604
如果你确实激活了延迟加载并且在使用范围之后发生了异常,那么请参阅https://*.com/a/21406579/870604
#1
24
Your code:
你的代码:
public Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
is disposing the repository before returning a Task
. If you make the code async
:
在返回任务之前处置存储库。如果您使代码异步:
public async Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return await langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
then it will dispose the repository just before the Task
completes. What actually happens is when you hit the await
, the method returns an incomplete Task
(note that the using
block is still "active" at this point). Then, when the langRepo.Add
task completes, the Post
method resumes executing and disposes the langRepo
. When the Post
method completes, the returned Task
is completed.
然后它将在任务完成之前处置存储库。实际发生的是当你点击等待时,该方法返回一个不完整的任务(注意此时使用块仍然是“活动的”)。然后,当langRepo.Add任务完成时,Post方法继续执行并释放langRepo。 Post方法完成后,返回的Task完成。
For more information, see my async
intro.
有关更多信息,请参阅我的异步介绍。
#2
4
I would go for the 'one DbContext per request' way, and reuse the DbContext within the request. As all tasks should be completed at the end of the request anyway, you can safely dispose it again.
我会选择“每个请求一个DbContext”方式,并在请求中重用DbContext。由于所有任务都应在请求结束时完成,您可以再次安全地处理它。
See i.e.: One DbContext per request in ASP.NET MVC (without IOC container)
参见:ASP.NET MVC中每个请求一个DbContext(没有IOC容器)
Some other advantages:
其他一些优点:
- some entities might already be materialized in the DbContext from previous queries, saving some extra queries.
- 某些实体可能已经在之前的查询中在DbContext中实现,从而节省了一些额外的查询。
- you don't have all those extra
using
statements cluttering your code. - 你没有那些额外的使用语句混乱你的代码。
#3
1
If you are using proper n-tiered programming patters, your controller should never even know that a database request is being made. That should all happen in your service layer.
如果您使用正确的n层编程模式,您的控制器甚至不应该知道正在进行数据库请求。这应该都发生在你的服务层。
There are a couple of ways to do this. One is to create 2 constructors per class, one that creates a context and one that accepts an already existing context. This way, you can pass the context around if you're already in the service layer, or create a new one if it's the controller/model calling the service layer.
有几种方法可以做到这一点。一种是为每个类创建2个构造函数,一个用于创建上下文,另一个用于接受已存在的上下文。这样,如果您已经在服务层中,则可以传递上下文,如果是调用服务层的控制器/模型,则可以创建新上下文。
The other is to create an internal overload of each method and accept the context there.
另一种是创建每个方法的内部重载并接受那里的上下文。
But, yes, you should be wrapping these in a using.
但是,是的,你应该将它们包装在一个使用中。
In theory, the garbage collection SHOULD clean these up without wrapping them, but I don't entirely trust the GC.
理论上,垃圾收集应该清理它们而不包装它们,但我并不完全信任GC。
#4
1
I agree with @Dirk Boer that the best way to manage DbContext lifetime is with an IoC container that disposes of the context when the http request completes. However if that is not an option, you could also do something like this:
我同意@Dirk Boer的说法,管理DbContext生命周期的最佳方法是使用一个IoC容器,在http请求完成时处理上下文。但是,如果这不是一个选项,你也可以这样做:
var dbContext = new MyDbContext();
var results = await dbContext.Set<MyEntity>.ToArrayAsync();
dbContext.Dispose();
The using
statement is just syntactic sugar for disposing of an object at the end of a code block. You can achieve the same effect without a using
block by simply calling .Dispose
yourself.
using语句只是用于在代码块末尾处理对象的语法糖。只需调用.Dispose,就可以在没有使用块的情况下实现相同的效果。
Come to think of it, you shouldn't get object disposed exceptions if you use the await
keyword within the using block:
想想看,如果在using块中使用await关键字,则不应该获取对象处置异常:
public async Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
var returnValue = langRepo.Add(RequestOrganizationTypeEnum, language);
await langRepo.SaveChangesAsync();
return returnValue;
}
}
#5
0
IMHO, it's again an issue caused by usage of lazy-loading. After you disposed your context, you can't lazy-load a property anymore because disposing the context closes the underlying connection to the database server.
恕我直言,这又是一个由懒惰加载引起的问题。在处理完上下文后,您不能再延迟加载属性,因为处理上下文会关闭与数据库服务器的基础连接。
If you do have lazy-loading activated and the exception occurs after the using
scope, then please see https://*.com/a/21406579/870604
如果你确实激活了延迟加载并且在使用范围之后发生了异常,那么请参阅https://*.com/a/21406579/870604