2010-06-25 65 views
1

在.NET 4.0和Linq to SQL中,我嘗試使用分部類從更新方法(現有的DBML方法)中「觸發」更改。爲簡單起見,假設同列Id和價值當擴展Linq部分方法時,SQL Server超時異常

自動根DBML包含方法OnValueChanged一個桌上的東西,我會延長該和作爲一個練習嘗試在另一個行改變一個值:

public partial class Things 
    { 
     partial void OnValueChanged() 
     { 
      MyAppDataContext dc = new MyAppDataContext(); 
      var q = from o in dc.GetTable<Things>() where o.Id == 13 select o; 
      foreach (Things o in q) 
      { 
       o.Value = "1"; // try to change some other row 
      } 
      try 
      { 
       dc.SubmitChanges(); 
      } 
      catch (Exception) 
      { 
       // SQL timeout occurs 
      } 
     } 
    } 

發生SQL超時錯誤。我懷疑datacontext在當前OnValueChanged()方法已經處理它的datacontext之前嘗試SubmitChanges()變得困惑,但我不確定。

大多數情況下,我找不到在現有的DBML生成的方法中針對數據庫觸發更新的良好模式的示例。

任何人都可以提供任何指示爲什麼這不起作用,以及我如何能夠完成某些工作正常嗎? (我意識到我可以在SQL數據庫中觸發,但不想走這條路。)

謝謝!

回答

3

首先,您在您的功能中根本沒有處置DataContext。將其包裝在using聲明中。

實際的問題來自於您通過在檢索值上設置Value屬性來遞歸調用自己的事實。您只需進入暫停狀態,然後您就可以點擊StackOverflowException

目前還不清楚你在這裏要做什麼;如果您試圖在您將Value屬性設置爲此處與其他任何地方時允許不同的行爲,那麼使用標誌就足夠簡單了。在您的部分類中,在更新值之前聲明一個名爲UpdatingValue的實例布爾型自動屬性internal,並在foreach塊中的每個項目上將其設置爲true,然後在更新值之後將其設置爲false。然後,作爲OnValueChanged的第一行,請檢查以確保UpdatingValuefalse

像這樣:

public partial class Things 
{ 
    internal bool UpdatingValue { get; set; } 

    partial void OnValueChanged() 
    { 
     if (UpdatingValue) return; 

     using(MyAppDataContext dc = new MyAppDataContext()) 
     { 
      var q = from o in dc.GetTable<Things>() where o.Id == 13 select o; 
      foreach (Things o in q) 
      { 
       o.UpdatingValue = true; 
       o.Value = "1"; // try to change some other row 
       o.UpdatingValue = false; 
      } 

      dc.SubmitChanges(); 
     } 
    } 
} 
0

我會懷疑你也可以通過物聯網的OnValueChanged事件處理程序改變事物的價值觀引入無限遞歸。

對我來說,一個更清潔的解決問題的方法是不產生你在DBML文件類,而是你創建一個類使用LinqToSql attributes。通過這樣做,您可以在屬性/列的設置器中進行「觸發」修改。

0

我有類似的問題。我不認爲它是你的代碼中的一個錯誤,我傾向於SqlDependency如何工作的錯誤。我和你做了同樣的事情,但我逐步測試了它。如果select語句返回1-100行,那麼它工作正常。如果select語句返回1000行,那麼我會得到SqlException(超時)。

這不是一個堆棧溢出問題(至少不在此客戶端代碼中)。在OnValueChanged事件處理程序中放置一個斷點表明,在SubmitChanges調用掛起時它不會再次被調用。

在調用SubmitChanges之前,有可能需要OnValueChanged調用必須返回。也許在另一個線程上調用SubmitChanges可能會有所幫助。

我的解決方案是將代碼包裝在一個大的try/catch塊中以捕獲SqlException。如果發生,那麼我執行相同的查詢,但我不使用SqlDependency,也不要將它附加到該命令。這不會掛起SubmitChanges調用了。之後,我重新創建SqlDependency,然後再次進行查詢,重新註冊依賴關係。

這並不理想,但至少它會最終處理所有的行。只有在需要選擇很多行的情況下才會出現問題,並且如果程序運行正常,則不會發生這種情況,因爲它會不斷追趕。

public Constructor(string connString, CogTrkDBLog logWriter0) 
    { 
     connectionString = connString; 
     logWriter = logWriter0; 

     using (SqlConnection conn = new SqlConnection(connString)) 
     { 
      conn.Open(); 
      using (SqlCommand cmd = new SqlCommand("SELECT is_broker_enabled FROM sys.databases WHERE name = 'cogtrk'", conn)) 
      { 
       bool r = (bool) cmd.ExecuteScalar(); 
       if (!r) 
       { 
        throw new Exception("is_broker_enabled was false"); 
       } 
      } 
     } 
     if (!CanRequestNotifications()) 
     { 
      throw new Exception("Not enough permission to run"); 
     } 


     // Remove any existing dependency connection, then create a new one. 
     SqlDependency.Stop(connectionString); 
     SqlDependency.Start(connectionString); 

     if (connection == null) 
     { 
      connection = new SqlConnection(connectionString); 
      connection.Open(); 
     } 

     if (command == null) 
     { 
      command = new SqlCommand(GetSQL(), connection); 
     } 

     GetData(false); 
     GetData(true); 
    } 


    private string GetSQL() 
    { 
     return "SELECT id, command, state, value " + 
     " FROM dbo.commandqueue WHERE state = 0 ORDER BY id"; 

    } 
    void dependency_OnChange(object sender, SqlNotificationEventArgs e) 
    { 
     // Remove the handler, since it is only good 
     // for a single notification. 
     SqlDependency dependency = (SqlDependency)sender; 
     dependency.OnChange -= dependency_OnChange; 

     GetData(true); 
    } 

    void GetData(bool withDependency) 
    { 
     lock (this) 
     { 
      bool repeat = false; 
      do { 
       repeat = false; 
       try 
       { 
        GetDataRetry(withDependency); 
       } 
       catch (SqlException) 
       { 
        if (withDependency) { 
         GetDataRetry(false); 
         repeat = true; 
        } 
       } 
      } while (repeat); 
     } 
    } 

    private void GetDataRetry(bool withDependency) 
    { 
     // Make sure the command object does not already have 
     // a notification object associated with it. 
     command.Notification = null; 

     // Create and bind the SqlDependency object 
     // to the command object. 

     if (withDependency) 
     { 
      SqlDependency dependency = new SqlDependency(command); 
      dependency.OnChange += dependency_OnChange; 
     } 


     Console.WriteLine("Getting a batch of commands"); 
     // Execute the command. 
     using (SqlDataReader reader = command.ExecuteReader()) 
     { 
      using (CommandQueueDb db = new CommandQueueDb(connectionString)) 
      { 
       foreach (CommandEntry c in db.Translate<CommandEntry>(reader)) 
       { 
        Console.WriteLine("id:" + c.id); 
        c.state = 1; 
        db.SubmitChanges(); 
       } 
      } 
     } 
    }