簡體   English   中英

實體框架不會更新由觸發器修改的值

[英]Entity Framework doesn't update value which is modified by a trigger

我的表Sections (SQL Server)具有ID作為主鍵(int, identity)SortIndex列(int)以進行排序。

數據庫有一個觸發器, SortIndex := ID在每個INSERT設置SortIndex := ID 顯然,我想稍后通過交換兩行的值來更改排序索引。

我使用Entity Framework訪問數據,所有這些都使用MVC3 Web應用程序。

問題是,在我將新對象插入表后,Entity Framework不會更新SortIndex的值。 它還會緩存所有數據,因此以下調用從此表中獲取所有對象也會為此對象提供錯誤的SortIndex值。

我嘗試在EDMX為此列更改StoreGeneratedPattern 這似乎是偉大而優雅的,但並沒有解決問題。

如果我設置為Identity ,則會導致EF正確更新該值,但它將變為只讀(嘗試更改時拋出異常)。 將其設置為Computed類似,但不是拋出異常,而是不將值寫入DB。

如果我需要在插入對象后使用它,我可以每次重新創建EF對象,只需執行以下操作:

DatabaseEntities db = new DatabaseEntities()

但對我來說這似乎是一種丑陋的解決方法。

這個問題的解決方案是什么?

顯然是一些東西,不需要我在每次insert后做一些動作(並冒一個被遺忘和不被注意的風險)是首選。

簡而言之, StoreGeneratedPattern意味着:該值由商店處理,您的應用程序永遠不會修改它。 在這種情況下,您將在調用SaveChanges后自動獲得商店生成的值。

如果您不使用StoreGeneratedPattern ,則無法獲得價值,您必須強制執行另一個查詢以刷新您的實體。 你可以這樣做:

objectContext.Refresh(RefreshMode.StoreWins, yourSection);

通常,您需要通過觸發器和應用程序更新數據庫中的值的情況與EF(以及可能還有其他ORM工具)不能很好地協同工作。

我發現'Ladislav Mrnka'的答案是准確的並且標記為接受。 以下是我在嘗試尋找解決方案時發現的其他解決方法。 但是,我一直在尋找的解決方案通常是不可能的。

其中一種可能性是設置StoreGeneratedPattern = Computed以讓EF知道,計算此值。 然后,創建一個存儲過程來實際更改SortIndex的值。 通常,它會更改兩行中的值(交換它們),以更改排序順序。 此過程以及INSERT的觸發器可確保數據在數據庫中保持一致。 如果沒有在SortIndex設置適當的值,則無法創建新行,不可能使兩個對象具有相同的值(除非存儲過程有錯誤),並且無法以某種方式手動中斷該值,因為無法編輯通過EF。 看起來很棒的解決方案。

可以很容易地將存儲過程映射到EF中的函數。

問題是,現在可以輸入新行並且EF正確更新其緩存中的數據,但是在調用存儲過程后緩存不會更新。 仍然需要一些手動更新或刷新功能。 否則,以下調用以獲取按SortIndex排序的SortIndex將產生錯誤的結果。

除此之外,可以為多個實體設置MergeOption = MergeOption.OverwriteChanges ,這會使EF更好地更新數據庫中的數據。 完成此操作后,可以在插入或調用存儲過程后重新讀取對象,它將被刷新。 但是,使用db.Section.OrderBy(o => o.SortIndex)讀取對象集合仍將返回錯誤排序順序的緩存結果。

如果有人感興趣,可以通過添加EF OnContextCreated類和部分方法OnContextCreated使MergeOption默認為其他東西,如下所示:

public partial class DatabaseEntities
{
    partial void OnContextCreated()
    {
        Subsection.MergeOption = MergeOption.OverwriteChanges;
        Section.MergeOption = MergeOption.OverwriteChanges;
        Function.MergeOption = MergeOption.OverwriteChanges;
    }
}

我有一個與Sql Server Quote表類似的情況,其中varchar QuoteNumber列是一個非主要唯一鍵,其值由插入后觸發器生成。 使用觸發器是因為生成的值是通過從外鍵表中獲取數據而得到的。 Sql Server架構標識聲明不允許您從其他表中提取信息。

我希望EF將此varchar列視為標識,並在更新時對其執行任何操作,並在插入后重新讀取它。 如果.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)屬性在它生成的代碼中的非標識列中配置實體(如右圖所示),EF將執行此操作:

public QuoteConfiguration(string schema)
{
    ToTable("Quote", schema);
    HasKey(x => x.ID);

    Property(x => x.ID).HasColumnName(@"ID").HasColumnType("int").IsRequired().HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
    Property(x => x.QuoteNumber).HasColumnName(@"Quote_Number").HasColumnType("varchar").IsOptional().IsUnicode(false).HasMaxLength(64).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
}

我的EF模型首先是代碼,由Simon Hughes的EntityFramework Reverse POCO Generator生成。 起初,我無法弄清楚如何使生成器將此屬性添加到未在Sql Server中聲明為標識的列。

插入后重讀整個Quote實體不會檢索自動生成的QuoteNumber。 然后我發現在插入后重新讀取QuoteNumber列擊敗了實體緩存。 但是,我覺得這樣做很臟。

最后,我與Simon Hughes一起探討如何讓他的EF Reverse POCO為我做這件事。 您只需在* .tt文件中擴展UpdateColumn函數,如下所示:

Settings.UpdateColumn = (Column column, Table table) =>
{
        if (table.Name.Equals("Quote", StringComparison.InvariantCultureIgnoreCase)
            && column.Name.Equals("Quote_Number", StringComparison.InvariantCultureIgnoreCase))
        {
            column.IsStoreGenerated = true;
        }
}

你知道你是否會在同一個請求中再次使用該列?

我將使用每個請求場景的上下文,這通常可以解決許多問題,因為每個請求都會創建一個新的EF上下文,因此每個請求都會有一個新數據。

對於長期存在的背景,如您所述,可能會出現緊張情緒。

無論如何,StoreGeneratedPattern設置為計算應該是正確的。 但它只在您存儲實際實體時才會更新。 它不是通過插入或更新任何其他實體來更新的。

來自http://msdn.microsoft.com/en-us/library/dd296755(v=vs.90).aspx

如果創建新實體或更改現有實體,則在應用程序中調用SaveChanges方法時,將從服務器檢索StoreGeneratedPattern設置為Computed的屬性值。 如果為應用程序中StoreGeneratedPattern設置為Computed的屬性分配值,則在調用SaveChanges方法時,將使用服務器生成的值覆蓋該值。

我們正在使用SQL序列GUID的計算值選項,它正常工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM