[.net] 在SQL Server Compact Edition數據庫上,如何解決LINQ to SQL中的“行未找到或已更改”異常?


Answers

DataContext上有一個名為Refresh的方法,可能對此有所幫助。 它允許您在提交更改之前重新加載數據庫記錄,並提供不同的模式以確定保留哪些值。 “KeepChanges”似乎對我來說是最聰明的,它旨在將我的更改與數據庫中發生的任何非衝突更改合併在一起。

如果我理解正確。 :)

Question

在使用LINQ to SQL連接(針對SQL Server Compact Edition)更新多個屬性後,執行SubmitChanges到DataContext時,出現“找不到或未更改的行”。 ChangeConflictException。

var ctx = new Data.MobileServerDataDataContext(Common.DatabasePath);
var deviceSessionRecord = ctx.Sessions.First(sess => sess.SessionRecId == args.DeviceSessionId);

deviceSessionRecord.IsActive = false;
deviceSessionRecord.Disconnected = DateTime.Now;

ctx.SubmitChanges();

該查詢生成以下SQL:

UPDATE [Sessions]
SET [Is_Active] = @p0, [Disconnected] = @p1
WHERE 0 = 1
-- @p0: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p1: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:12:02 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

顯而易見的問題是WHERE 0 = 1 ,在加載記錄之後,我已經確認“deviceSessionRecord”中的所有屬性都包含主鍵是正確的。 此外,當捕獲“ChangeConflictException”時,沒有關於此失敗原因的附加信息。 我也確認了這個異常在數據庫中只有一條記錄(我試圖更新的記錄)

奇怪的是,我在不同的代碼段中有一個非常相似的更新語句,它會生成以下SQL並確實更新我的SQL Server Compact Edition數據庫。

UPDATE [Sessions]
SET [Is_Active] = @p4, [Disconnected] = @p5
WHERE ([Session_RecId] = @p0) AND ([App_RecId] = @p1) AND ([Is_Active] = 1) AND ([Established] = @p2) AND ([Disconnected] IS NULL) AND ([Member_Id] IS NULL) AND ([Company_Id] IS NULL) AND ([Site] IS NULL) AND (NOT ([Is_Device] = 1)) AND ([Machine_Name] = @p3)
-- @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [0fbbee53-cf4c-4643-9045-e0a284ad131b]
-- @p1: Input Guid (Size = 0; Prec = 0; Scale = 0) [7a174954-dd18-406e-833d-8da650207d3d]
-- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:50 PM]
-- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [CWMOBILEDEV]
-- @p4: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p5: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:52 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

我已經確認在數據庫架構和生成LINQ類的DBML中都確定了適當的主要字段值。

我想這幾乎是一個兩部分問題:

  1. 為什麼拋出異常?
  2. 在查看第二組生成的SQL之後,看起來好像是為了檢測衝突,檢查所有的字段是很好的,但我想這會是相當低效的。 這是否總是有效? 是否有一個設置來檢查主鍵?

過去兩個小時我一直在與此戰鬥,所以任何幫助將不勝感激。




在我的情況下,問題出在服務器範圍的用戶選項上。 以下:

https://msdn.microsoft.com/en-us/library/ms190763.aspx

我啟用了NOCOUNT選項,希望獲得一些性能優勢:

EXEC sys.sp_configure 'user options', 512;
RECONFIGURE;

而事實證明這是為了打破Linq對受感染行的檢查(盡可能從.NET資源中找出它),導致ChangeConflictException

重置選項以排除512位可解決問題。




這是你需要重寫C#代碼上的這個錯誤:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }



我最近遇到了這個錯誤,並發現問題不在我的數據上下文中,而是在上下文上調用Commit之後觸發內部觸發器的更新語句。 觸發器試圖用一個空值更新一個不可為空的字段,並且它導致上下文與上面提到的消息一起出錯。

我添加這個答案只是為了幫助其他人處理這個錯誤,而不是在上面的答案中找到解決方案。




這也可能是由於使用多個DbContext引起的。

例如:

protected void loginUser(string username)
{
    var db = new AppDbContext();
    var user = db.Users.Single(u => u.Username == username);
    user.LastLogin = DateTime.UtcNow;
    db.SubmitChanges();
}

protected void doSomething(object obj)
{
    string username = "joe";
    var db = new AppDbContext();
    var user = db.Users.Single(u => u.Username == username);

    if (DateTime.UtcNow - user.LastLogin > new TimeSpan(0, 30, 0))
        loginUser(username);

    user.Something = obj;
    db.SubmitChanges();
}

這段代碼不時會失敗,看起來不可預知,因為用戶在兩種情況下都使用,更改並保存在一個中,然後保存在另一個中。 擁有“Something”的用戶的內存中表示與數據庫中的內容不匹配,因此您會收到此潛伏的錯誤。

如果有一種工具可以追踪這些情況,那將會很不錯。 對於Production中的性能來說,這顯然是非常糟糕的,但是一旦你有幾層邏輯都與同一個數據庫進行交互,這些可能會是非常陰險的bug。 我認為這將是一個包裝,它維護一個線程安全的哈希表,一旦它看到兩個上下文同時跟踪同一個對象(並拋出清楚的例外,說明為什麼和什麼對象),就會被跟踪並失敗。




我不知道你是否已經找到你的問題的任何滿意的答案,但我發布了類似的問題,並最終自己回答。 事實證明,數據庫的NOCOUNT默認連接選項已打開,導致使用Linq to Sql進行的每個更新都會導致ChangeConflictException。 你可以在here參考我的文章。