2010-06-25 62 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(); 
       } 
      } 
     } 
    }