DbContext怎么在Asp.mvc中使用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Repository
  {
      //實(shí)例化EF容器:有弊端。一個(gè)線程里可能會(huì)創(chuàng)建多個(gè)DbContext
      //DbContext db = new DbContext();
 
      //改造:保證一個(gè)請(qǐng)求線程中只有一份EF容器(你要明白:一個(gè)url請(qǐng)求到服務(wù)器,IIS就開(kāi)一個(gè)線程去處理)
      protected DbContext GetDbContext
      {
          get
          {
              //向線程緩存中查詢,如果返回的是null,則創(chuàng)建,同時(shí)存入到這個(gè)線程緩存中
              //注意的是線程緩存CallContext,而不是我們熟悉的HttpRuntime.cache。意味著這個(gè)DbContext對(duì)象在這個(gè)線程內(nèi)能被其他方法共享。
              object efDbContext = CallContext.GetData("DbContext");
              if (efDbContext == null)
              {
                  efDbContext = new DbContext();
                  //存入到這個(gè)線程緩存中
                  CallContext.SetData("DbContext", efDbContext);
              }
              return efDbContext as DbContext;
          }
      }
}

  這么定義之后,所有需要用到DbContext對(duì)象的地方,都調(diào)這個(gè)方法。

2. 不要隨便using或Dispose DbContext會(huì)導(dǎo)致延遲加載的不可用,還會(huì)有一些其他錯(cuò)誤 如IQueryable<T> 下面的方法(.First() /.Count())也不能用。

情況一:

  a寫(xiě)法結(jié)果正確

1
2
3
PhoneBookEntities phoneBookEntities = new PhoneBookEntities();
var ci = phoneBookEntities.ContactInfo.FirstOrDefault();//這里并沒(méi)有拿到ci對(duì)象里面的GroupInfo屬性。
if (ci != null) MessageBox.Show(ci.GroupInfo.GroupName);//是延遲加載,訪問(wèn) ci.GroupInfo.GroupName 才會(huì)去數(shù)據(jù)庫(kù)查數(shù)據(jù)

  b寫(xiě)法報(bào)錯(cuò)

1
2
3
4
5
6
7
8
ContactInfo ci = null;
using (PhoneBookEntities phoneBookEntities = new PhoneBookEntities())
{
    ci = phoneBookEntities.ContactInfo.FirstOrDefault();
}
 
if (ci != null) MessageBox.Show(ci.GroupInfo.GroupName);
//報(bào)錯(cuò):此ObjectContext實(shí)例已釋放,不可再用于需要連接的操作。意味著延遲加載不可用。

情況二:

  a寫(xiě)法報(bào)錯(cuò)

1
2
3
4
5
6
7
IQueryable<ContactInfo> ci = null;
using (PhoneBookEntities phoneBookEntities = new PhoneBookEntities())
{
    ci = phoneBookEntities.ContactInfo.Where(c => true);
}
 
if (ci != null) MessageBox.Show(ci.Count().ToString());//報(bào)錯(cuò):提示DbContext已經(jīng)釋放。

  b寫(xiě)法正確

1
2
3
4
5
6
IQueryable<ContactInfo> ci = null;
using (PhoneBookEntities phoneBookEntities = new PhoneBookEntities())
{
    ci = phoneBookEntities.ContactInfo.Where(c => true);
    if (ci != null) MessageBox.Show(ci.Count().ToString());//可以返回正確結(jié)果。
}

  

3.為什么你要using 或dispose掉DbContext ?

是擔(dān)心數(shù)據(jù)庫(kù)連接沒(méi)有釋放?還是擔(dān)心DbContext占用過(guò)多資源呢?
首先擔(dān)心數(shù)據(jù)庫(kù)連接沒(méi)有釋放肯定是多余的,因?yàn)镈bContext在SaveChanges完成后會(huì)釋放掉打開(kāi)的數(shù)據(jù)庫(kù)連接。
可以反編譯一下SaveChages的源碼。
擔(dān)心DbContext占用過(guò)多資源也是多余的,有GC回收。

結(jié)論,You can call Dispose, but in most common scenarios you don’t need to.

更詳細(xì)的可以看這個(gè)英文博客的文章,其中有 Diego Vega (the Senior SDE Lead on EF) 的回信

Hello Jon,

The default behavior of DbContext is that the underlying connection is automatically opened any time is needed and closed when it is no longer needed. E.g. when you execute a query and iterate over query results using “foreach”, the call to IEnumerable<T>.GetEnumerator() will cause the connection to be opened, and when later there are no more results available, “foreach” will take care of calling Dispose on the enumerator, which will close the connection. In a similar way, a call to DbContext.SaveChanges() will open the connection before sending changes to the database and will close it before returning.

Given this default behavior, in many real-world cases it is harmless to leave the context without disposing it and just rely on garbage collection.

That said, there are two main reason our sample code tends to always use “using” or dispose the context in some other way:

1. The default automatic open/close behavior is relatively easy to override: you can assume control of when the connection is opened and closed by manually opening the connection. Once you start doing this in some part of your code, then forgetting to dipose the context becomes harmful, because you might be leaking open connections.

2. DbContext implements IDiposable following the recommended pattern, which includes exposing a virtual protected Dispose method that derived types can override if for example the need to aggregate other unmanaged resources into the lifetime of the context.

 

By the way, with DbContext the pattern to open the connection manually and override the automatic open/close behavior is a bit awkward:

((IObjectContextAdapter)dbContext).ObjectContext.Connection.Open()

But we have a bug to make this easier as it used to be with ObjectContext before, e.g.:

dbContext.Database.Connection.Open()

Hope this helps,

Diego