2012-04-09 80 views
2

我有一個小項目,我正在運行MVC3。 我使用LINQ從數據庫中獲取數據。 我使用與MVC3附帶的預製示例相同的架構設計構建了我的項目。 在這樣的項目中,應用程序被分割,在這個主題中,我想關注Model.cs文件。目前,我對每個控制器都有一個,所以作爲一個例子,我有一個HighscoreController.cs和一個HighscoreModels.cs。在模型類中,我定義了一個Service類,該類引用了datacontext以及一些使用此datacontext查詢數據庫的方法。 現在我遇到了這些方法中的一些正在執行相同查詢的問題,因此我想讓訪問數據庫的中心點,所以我想我會實現存儲庫模式,所以我做了。 因此,而不是在服務類具有一個DataContext的引用我現在有這樣的資源庫的引用:MVC,存儲庫模式和DataLoadOptions

private IRepository _repository; 

    public HighscoreService() 
     : this(new Repository()) 
    { } 

    public HighscoreService(IRepository repository) 
    { 
     _repository = repository; 
    } 

現在數據庫中調用存儲庫中進行處理和存儲庫從服務類中使用通過_repository參考。

我的倉庫是建立這樣的:

public class Repository : IRepository 
    { 
    private MyDataContext _dataContext; 

    public Repository() 
    { 
     _dataContext = new MyDataContext(); 
    } 

    public Member MemberByName(string memberName) 
    { 
     Member member = CompiledQueries.MemberByName(_dataContext, memberName); 
     return member; 
    } 
    } 

當我嘗試結合使用DataLoadOptions這個倉庫模式我所面臨的問題出現。

因爲當您使用dataloadoptions時,在應用新的dataloadoptions之前,您一定沒有對datacontext進行過查詢。由於我的存儲庫在所有方法中都重用了數據上下文,這根本不起作用。 我一直在嘗試2件事,一種是通過using語句重新創建每個方法中的datacontext,以確保每次刷新datacontext。但是,當我從存儲庫中取回結果返回到我的模型中並且範圍在存儲庫模式內部運行時,隨着using語句結束,我遇到了問題,這意味着結果不能用於例如。 .Count()或.ToList()是因爲爲我提供數據的datacontext已終止。我還嘗試了另一種解決方案,它在整個存儲庫中使用相同的datacontext,但在每個使用dataloadoptions的方法中創建一個新實例。這感覺非常骯髒;) 所以任何人都可以給我一個關於如何使用DataLoadOptions與存儲庫模式的建議?並避免我剛剛描述的問題。或者我應該不使用dataloadoptions並選擇另一種方式來做到這一點?我使用DataLoadOptions的原因是我想從相關表中獲取一些數據。

作爲一個小問題:在上面的代碼示例中,您可以看到我已將CompiledQueries放入自己的.cs文件中。這是一個糟糕的設計?在MVC應用程序中將編譯查詢放在哪裏有任何指導原則嗎?

感謝您的閱讀並希望我的問題有一些答案;)提前致謝。如果您需要更多信息,只需詢問。

+0

我不是專家,但我認爲將數據上下文保留在「請求範圍」中的想法可能有所幫助。這樣,每個HTTP請求都會創建一個新的上下文,這可能會消除您的一些問題。像Ninject這樣的IoC容器可以幫助解決這個問題。雖然它涉及實體框架,但以下文章可能會有所幫助:http://buildstarted.com/2010/08/24/dependency-injection-with-ninject-moq-and-unit-testing/ – ngm 2012-04-09 13:13:22

+0

注入您的存儲庫並進行設置按照WebRequest的生活方式。目前還不確定存儲庫爲您做了什麼,除了添加無用的抽象層。 – CrazyCoderz 2012-04-09 20:45:00

回答

0

我絕不是DataLoadOptions的專家,但是從您的問題以及我讀到的內容來看,似乎您需要使用它來進行急切的加載。參考:

「因爲當您使用dataloadoptions時,在應用新的dataloadoptions之前,您不能在datacontext上進行以前的查詢。」

..對我來說這聽起來像是一個缺點或設計缺陷DataLoadOptions(我個人使用實體框架,而不是LINQ to SQL)。雖然我認爲按照ngm和CrazyCoderz的前兩條評論中提供的每個HTTP請求提供單個數據上下文是個不錯的主意,但我認爲這不會解決您的問題。如果您希望在單個HTTP請求中重複使用單個數據上下文,只要您執行第一個查詢,就好像您將無法將數據上下文中的DataLoadOptions設置爲新值。

我在vslive vegas上看到一個演示文稿,演示者提供了您提到的一種解決方案,在每個存儲庫方法中創建一個新的數據上下文。你需要做的是在之前調用ToList()或ToArray()來終止using語句並返回方法結果。

正如您所提到的,這會使對象在方法返回後無法預先加載到枚舉中。不過,如果你已經擁有了執行查詢和轉換爲ListCollectionArray,或其他一些具體IEnumerable,你並不需要訪問Count()ToList()方法任何更長的時間。相反,您可以使用Array.LengthList.CountCollection.Count屬性

還有什麼阻止您在每個存儲庫方法中創建新的數據上下文?意思是,在執行存儲庫方法之後,你需要什麼數據上下文,因爲它已被處置而無法獲取?

回覆評論

對於第一個查詢,你可以做到這一點?

public Member GetSomeRandomMember() 
{ 
    Member[] members = null; 
    using (var context = new MyDataContext()) 
    { 
     // execute the query to get the whole table 
     members = context.Members.ToArray(); 
    } 

    // do not need to query again 
    var totalRows = members.Length; 
    var skipThisMany = PerformRandomNumberComputation(totalRows); 
    return members.Skip(skipThisMany).FirstOrDefault(); 
} 

如果您的Members表有很多行,則可能不是最優的。在這種情況下,您希望執行2個查詢 - 1個進行計數,第二個進行選擇。你可以通過打開兩個上下文來實現:

public Member GetSomeRandomMember() 
{ 
    using (var context1 = new MyDataContext()) 
     var totalRows = context1.Members.Count(); 

    var skipThisMany = PerformRandomNumberComputation(totalRows); 

    Member member = null; 
    using (var context2 = new MyDataContext()) 
     member = context2.Members.Skip(skipThisMany).FirstOrDefault(); 

    return member; 
} 

對於你的評論的第二部分,我不確定我得到你在說什麼。數據和決策的變化給它的抓取都應該來在單個操作與單個上下文反正:

public void SaveMember(int id, string email, bool isSuspended) 
{ 
    using (var context = new MyDataContext()) 
    { 
     var member = context.Members.Single(m => m.Id == id); 
     member.Email = email; 
     member.IsSuspended = isSuspended; 
     context.SaveChanges(); // or whatever the linq to sql equivalent is 
    } 
} 

如果你想整個實體傳遞給庫方法,你還是應該查詢出來因此它被附加到正確的上下文中:

public void SaveMember(Member member) 
{ 
    var memberDto = member; 
    using (var context = new MyDataContext()) 
    { 
     member = context.Members.Single(m => m.Id == memberDto.Id); 
     member.Email = memberDto.Email; 
     member.IsSuspended = memberDto.IsSuspended; 
     context.SaveChanges(); // or whatever the linq to sql equivalent is 
    } 
} 
+0

感謝您的回答。現在我加載一個整個表格,然後對它進行計數,然後根據計數生成一個隨機數字,然後在相同的加載結果中用於.Skip(randomNumber).FirstOrDefault()。所以,如果我不能懶惰地加載它,那麼我不得不將它分成2個查詢,一個用於獲取計數,另一個用於獲取我的實際行。我終止上下文的另一個問題是獲取數據並對其進行更改並將其放回,因爲當上下文終止時,它無法正確地將更改插入到之前獲取的已加載結果中。 – emin 2012-04-10 05:53:15

+0

再次感謝您的答案和精彩的代碼示例。我不確定我對第一部分的看法,打開了很多數據上下文,因爲我不知道如何執行重要的操作。我想我希望能有一些更神奇的解決方案;)第二部分很有意義,我會確保我這樣做。謝謝 – emin 2012-04-10 10:51:24

+0

@emin,雖然我不認爲這是打開和處理2個數據上下文的理想選擇,但您可能需要權衡一個選項與另一個選項的性能。通過執行第一個代碼片段,你實際上是在db上執行'SELECT * FROM Members'。這將導致數據庫將所有行返回到內存中。如果您的會員表有數千或數百萬行,這將會很慢並且很昂貴。第二種選擇不是'S​​ELECT COUNT(*)FROM Members',然後'SELECT * FROM Members WHERE ID = @ID',並且當表中有很多行時,它將始終具有更高的性能。 – danludwig 2012-04-10 16:21:26