简体   繁体   English

实体框架,DBContext和using()+ async?

[英]Entity Framework, DBContext and using() + async?

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. 去年我为使用EF的客户编写了一个大型应用程序。 And during the development everything worked great. 在开发过程中,一切都很顺利。

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). 运行几天后,我的ASP.NET MVC 4进程占用了机器的所有资源(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. 我在网上搜索,看到你应该在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. 在一天中,我重构了所有using()代码,这解决了我的问题,因为这个过程依赖于稳定的内存使用。

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. 我首先没有围绕我的查询的原因是我从DbContext包含的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 . 现在,我已经开始研究一个新的大项目了解我脑后的所有知识,并且我一直在尝试.NET 4.5和EF 6 async的新功能await EF 6.0 has all these async methods (eg SaveChangesAsync , ToListAsync , and so on). EF 6.0具有所有这些异步方法(例如SaveChangesAsyncToListAsync等)。

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. 但是,当我现在在using()块中包含我的语句时,我得到了异常,在查询能够返回之前, DbContext was disposed了。 This is expected behaviour. 这是预期的行为。 The query runs async and the using block is finished ahead of the query. 查询运行async, using块在查询之前完成。 But how should I dispose of my context in a proper way while using the async and await functions of ef 6?? 但是在使用ef 6的async和await函数时,我应该如何以适当的方式处理我的上下文?

Please point me in the right direction. 请指出我正确的方向。

Is using() needed in EF 6? 在EF 6中需要using()吗? Why do Microsoft's own examples never feature that? 为什么微软自己的例子从未有过这样的特色? How do you use async features and dispose of your context properly? 如何正确使用异步功能并处理上下文?

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 . 在返回Task之前处置存储库。 If you make the code async : 如果您使代码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. 然后它将在Task完成之前处置存储库。 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). 实际发生的是当你点击await ,该方法返回一个不完整的Task (注意此时using块仍然是“活动的”)。 Then, when the langRepo.Add task completes, the Post method resumes executing and disposes the langRepo . 然后,当langRepo.Add任务完成时, Post方法继续执行并释放langRepo When the Post method completes, the returned Task is completed. Post方法完成后,返回的Task完成。

For more information, see my async intro . 有关更多信息,请参阅我的async介绍

I would go for the 'one DbContext per request' way, and reuse the DbContext within the request. 我会选择“每个请求一个DbContext”方式,并在请求中重用DbContext。 As all tasks should be completed at the end of the request anyway, you can safely dispose it again. 由于所有任务都应在请求结束时完成,您可以再次安全地处理它。

See ie: One DbContext per request in ASP.NET MVC (without IOC container) 请参阅ie: 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. 你没有那些额外的using语句混乱你的代码。

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. 同意@Dirk Boer的说法,管理DbContext生命周期的最佳方法是使用一个IoC容器来处理http请求完成时的上下文。 However if that is not an option, you could also do something like this: 但是,如果这不是一个选项,你也可以这样做:

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. using语句只是用于在代码块末尾处理对象的语法糖。 You can achieve the same effect without a using block by simply calling .Dispose yourself. 只需调用.Dispose自己就可以在没有using块的情况下实现相同的效果。

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;
    }
}

If you are using proper n-tiered programming patters, your controller should never even know that a database request is being made. 如果您使用正确的n层编程模式,您的控制器甚至不应该知道正在进行数据库请求。 That should all happen in your service layer. 这应该都发生在你的服务层。

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. 一种是为每个类创建2个构造函数,一个用于创建上下文,另一个用于接受已存在的上下文。 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. 这样,如果您已经在服务层中,则可以传递上下文,如果是调用服务层的控制器/模型,则可以创建新上下文。

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。

If you want to keep your method synchronous but you want to save to DB asynchronously, don't use the using statement. 如果要保持方法同步但想要异步保存到DB,请不要使用using语句。 Like @danludwig said, it is just a syntactic sugar. 就像@danludwig所说,它只是一种语法糖。 You can call the SaveChangesAsync() method and then dispose the context after the task is completed. 您可以调用SaveChangesAsync()方法,然后在任务完成后处置上下文。 One way to do it is this: 一种方法是:

//Save asynchronously then dispose the context after
context.SaveChangesAsync().ContinueWith(c => context.Dispose());

Take note that the lambda you pass to ContinueWith() will also be executed asynchronously. 请注意,传递给ContinueWith()的lambda也将异步执行。

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://stackoverflow.com/a/21406579/870604 如果你确实激活了延迟加载并且在using范围之后发生了异常 ,那么请参阅https://stackoverflow.com/a/21406579/870604

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM