2010-07-16 103 views
28

作爲新來的實體框架,我真的很困擾如何處理這組問題。在我目前正在進行的項目中,整個網站與EF模型大量集成。首先,使用依賴注入引導程序來控制對EF上下文的訪問。出於操作原因,我們無法使用DI庫。我刪除了它,並根據需要使用了上下文對象的單個實例的模型。我開始得到以下例外:.NET實體框架和事務

類型'XXX'已被映射多次。

我們得出的結論是上下文的不同實例導致了這個問題。然後我將上下文對象抽象爲每個線程/頁面正在訪問的單個靜態實例。我現在得到幾個有關交易的例外之一:

新事務是不允許的,因爲會話中有其他線程正在運行 。

事務操作無法執行,因爲有 掛起的請求處理此事務。

當分配給命令的 連接處於未決的本地事務中時,ExecuteReader需要該命令進行事務。 命令的Transaction屬性尚未初始化。

這些異常中的最後一個發生在加載操作上。我沒有試圖在失敗的線程上將上下文狀態保存回Db。還有另一個線程正在執行這樣的操作。

這些例外是最好的,但我設法讓網站進入新的連接被拒絕的狀態,由於交易鎖定。不幸的是我找不到例外細節。

我想我的第一個問題是,如果從靜態單個實例使用EF模型?另外,是否可以刪除EF中的事務處理?我一直在使用,但沒有成功TransactionScope對象試過......

說實話我很多困在這裏,並不能明白爲什麼(應該是什麼)相當簡單的操作,造成這樣的問題...

+0

相關:http://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why – Steven 2012-09-13 09:29:39

+0

這太糟糕了,你不能使用IOC引導程序,因爲解決方案與[Ninject](http ://www.ninject.org/)將是一個 「普通的」 實例綁定到_request scope_,如其他人建議:'kernel.Bind >()爲了>( ).InRequestScope();' - 是**'InRequestScope' ** – drzaus 2013-07-03 13:56:52

回答

50

在Web應用程序中創建全局ObjectContext非常糟糕。 ObjectContext類不是線程安全的。它圍繞着unit of work的概念構建,這意味着您可以使用它來運行單個用例:因此用於商業交易。它意味着處理一個單一的請求。

您會發生異常,因爲您爲每個請求創建一個新的事務,但嘗試使用相同的ObjectContext。你很幸運,ObjectContext檢測到這一點並引發異常,因爲現在你發現這是行不通的。

請想想爲什麼這不起作用。 ObjectContext包含數據庫中實體的本地緩存。它允許您進行一系列更改並最終將這些更改提交到數據庫。當使用單個靜態ObjectContext時,多個用戶在該對象上調用SaveChanges,應該如何知道究竟應該提交哪些內容以及哪些不應該提交?因爲它不知道,它會保存所有更改,但在那時另一個用戶可能仍在進行更改。幸運的是,EF或數據庫將失敗,因爲實體處於無效狀態。如果不幸的是,處於無效狀態的對象已成功保存到數據庫中,並且幾星期後可能會發現數據庫充滿了垃圾。 您的問題的解決方案是create at least one ObjectContext per request。雖然在理論上可以緩存在用戶會話對象上下文,這也是一個不錯的主意,因爲ObjectContext通常會活得太長,並且將包含陳舊的數據(因爲其內部緩存不會自動刷新)。

UPDATE

還要注意的是有每個線程一個ObjectContext是爲具有完整的Web應用程序的一個單一實例那樣糟糕。 ASP.NET使用線程池,這意味着在Web應用程序的生命週期中將創建有限數量的線程。這基本上意味着在那種情況下,那些實例在應用程序的整個生命週期中仍然存在,從而導致數據過時的相同問題。

你可能會認爲每個線程有一個DbContext實際上是線程安全的,但通常情況並非如此,因爲ASP.NET有一個異步模型,允許在不同的線程上完成請求,最新版本的MVC和Web API甚至允許任意數量的線程按順序處理單個請求)。這意味着啓動請求並創建ObjectContext的線程可以在初始請求完成之前就可以處理另一個請求。然而,該請求中使用的對象(例如網頁,控制器或任何業務類)仍可能引用該ObjectContext。由於新的Web請求在同一個線程中運行,它將獲得與舊請求所使用的實例相同的ObjectContext實例。這又會在您的應用程序中導致競爭狀況,並導致與全局實例相同的線程安全問題。

+1

那麼重要組成部分,實際上這就是我認爲我在做......我絕不會通常使用靜態數據的上下文對象,但被越來越絕望。事實證明,我有一個其他的錯誤,我有單一的上下文實例。然而,它們在一個靜態對象內,這似乎是原因。 2個小時我的生活不回來... 謝謝 – sicknote 2010-07-16 15:55:28

+19

只有兩個小時?那麼你是一個幸運的人:-) – Steven 2010-07-16 21:16:19

+1

什麼是面對這種問題的最佳選擇? – Romias 2014-03-11 19:12:17

4

至於你提到的「現場」,在你的問題,我認爲這是一個Web應用程序。靜態成員只存在一次爲整個應用程序,如果您使用的在整個應用程序的單一上下文實例單身型圖案,請求種種都將是在翻過整個應用程序的各種狀態。

單一的靜態上下文實例將無法正常工作,但每多線程上下文實例將是麻煩的,以及你不能混搭環境。你需要的是每個線程的單個上下文。我們在應用程序中使用依賴注入類型模式完成了此操作。我們的BLL和DAL類採取了上下文關係中的方法的參數,這樣你可以做一些象下面這樣:你選擇

using (TransactionScope ts = new TransactionScope()) 
{ 
    using (ObjectContext oContext = new ObjectContext("MyConnection")) 
    { 
     oBLLClass.Update(oEntity, oContext); 
    } 
} 

如果您需要在更新中調用其他BLL/DAL方法(或其他方法)你只需傳遞相同的上下文。這種方式更新/插入/刪除是原子的,單個方法中的任何東西都使用相同的上下文實例,但該實例未被其他線程使用。