在.NET 4.0和Linq to SQL中,我尝试使用部分类来从更新方法(现有DBML方法)中“触发”更改。为简单起见,想象一下具有列Id和值
的表格auto gen 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超时错误。我怀疑在当前的OnValueChanged()方法处理了它的datacontext之前,datacontext在尝试SubmitChanges()时感到困惑,但我不确定。
大多数情况下,我找不到一个好的模式示例,用于在现有DBML生成的方法中触发对DB的更新。
任何人都可以提供任何指示,说明为什么这不起作用以及我如何能够完成一些有效的工作? (我意识到我可以在SQL数据库中触发,但不想采用那条路线。)
谢谢!
答案 0 :(得分:3)
首先,您的功能中根本没有处理DataContext
。用using
语句包装它。
实际问题来自于您通过在检索到的值上设置Value
属性来递归调用自己的事实。您只是在遇到StackOverflowException
之前就已经进入超时状态。
目前还不清楚你在这里要做什么;如果你试图在这里设置Value
属性与其他任何地方之间允许不同的行为,那么使用标志就足够了。在您的部分类中,声明一个名为internal
的{{1}}实例布尔自动属性,并在更新值之前将UpdatingValue
块中的每个项目设置为true
,然后更新值后将其设置为foreach
。然后,作为false
中的第一行,检查以确保OnValueChanged
为UpdatingValue
。
像这样:
false
答案 1 :(得分:0)
我怀疑你可能通过更改Things的OnValueChanged事件处理程序中的Things值来引入无限递归。
对我而言,解决问题的一个更简洁的解决方案不是在DBML文件中生成您的类,而是在您创建的类上使用LinqToSql attributes。通过这样做,您可以在属性/列的setter中进行“触发”修改。
答案 2 :(得分: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();
}
}
}
}