我们使用PetaPoco作为SQL 2008数据库的数据访问工具。尝试在附加了触发器的表上插入/更新行时出现问题。
我们正在使用PetaPoco的db.Save(object);
显示的错误是: 目标表' the_table_with_a_trigger'如果语句包含没有INTO子句的OUTPUT子句,则DML语句不能具有任何已启用的触发器。
我们如何使用PetaPoco在具有触发器的表上插入/更新数据?
答案 0 :(得分:7)
感谢@Eduardo Molteni,你让我走上正确的道路来解决这个问题。显然,SQL Server 2008 R2中的已知问题是,如果表具有触发器,则插入中的OUTPUT命令将失败。但是,PetaPoco会自动将OUTPUT子句插入到表中AutoIncrement = true的任何插入的命令文本中。
我的解决方案(适用于SQL Server 2008 R2)如下:
1)转到PetaPoco.DatabaseTypes.SqlServerDatabaseType.GetInsertOutputClause函数
删除(注释掉)
\\return String.Format(" OUTPUT INSERTED.[{0}]", primaryKeyName);
这将删除" OUTPUT"来自SQL插入语句。现在,插入将发生在带触发器的表上。但是,现在PetaPoco无法从新插入的行中检索新的主键(标识)。
2)转到PetaPoco.Database.Insert函数。紧接在线之上:
object id = _dbType.ExecuteInsert(this, cmd, primaryKeyName);
添加一个新行,如下所示:
cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;";
object id = _dbType.ExecuteInsert(this, cmd, primaryKeyName);
新行(存在于PetaPoco中,但未被使用)将允许insert语句检索标识。
答案 1 :(得分:3)
我认为cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;";
会更好。 @@ IDENTITY可以为您提供在触发器中生成的ID,而不是您的声明。
答案 2 :(得分:1)
PetaPoco仅在Oracle DB中创建OUTPUT参数以获取新的AutoIncrement ID。
对于Sql Server,当您标记问题时,如果表格具有自动增量ID,则只会添加SELECT SCOPE_IDENTITY() AS NewID
。
PetaPoco.cs中的相关代码:
cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})",
EscapeTableName(tableName),
string.Join(",", names.ToArray()),
string.Join(",", values.ToArray())
);
if (!autoIncrement) {
DoPreExecute(cmd);
cmd.ExecuteNonQuery();
OnExecutedCommand(cmd);
return true;
}
object id;
switch (_dbType) {
case DBType.SqlServer:
cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;";
DoPreExecute(cmd);
id = cmd.ExecuteScalar();
OnExecutedCommand(cmd);
break;
尝试关闭自动增量ID并手动设置以查看问题是否消失
答案 3 :(得分:1)
因为我确信我不会成为遇到这个人的最后一个人...
我已经为新项目采用了PetaPoco,但我遇到了类似的问题,但是恢复到scope_identity()并没有开始工作。所以我:
1)扩展了IProvider接口。
/// <summary>
/// Return an SQL expression that can be used with <seealso cref="GetInsertPostScript(string)"/>
/// and <seealso cref="GetInsertOutputClause(string)"/> to return a provider-generated value from an INSERT; typically an IDENTITY
/// column in Microsoft SQL Server.
/// </summary>
/// <param name="primaryKeyName"></param>
/// <returns></returns>
string GetInsertPreamble(string primaryKeyName);
/// <summary>
/// Return an SQL expression that can be used with <seealso cref="GetInsertPreamble(string)"/>
/// and <seealso cref="GetInsertOutputClause(string)"/> to return a provider-generated value from an INSERT; typically an IDENTITY
/// column in Microsoft SQL Server.
/// </summary>
/// <param name="primaryKeyName"></param>
/// <returns></returns>
string GetInsertPostScript(string primaryKeyName);
2)将它们添加到DatabaseProvider.cs:
public virtual string GetInsertPreamble(string primaryKeyName)
{
return string.Empty;
}
public virtual string GetInsertPostScript(string primaryKeyName)
{
return string.Empty;
}
3)然后是SqlServerDatabaseProvider,包括更改现有的OUTPUT子句:
public override string GetInsertOutputClause(string primaryKeyName)
{
return String.Format(" OUTPUT INSERTED.[{0}] into @result({0})", primaryKeyName);
}
public override string GetInsertPreamble(string primaryKeyName)
{
return string.Format("DECLARE @result TABLE({0} sql_variant); ", primaryKeyName);
}
public override string GetInsertPostScript(string primaryKeyName)
{
return string.Format("; SELECT {0} FROM @result; ", primaryKeyName);
}
4)最后,将这些内容合并到Database.cs中:
...string outputClause = string.Empty;
string insertPreamble = string.Empty;
string insertPostScript = string.Empty;
if (autoIncrement)
{
insertPreamble = _provider.GetInsertPreamble(primaryKeyName, tableName);
outputClause = _provider.GetInsertOutputClause(primaryKeyName);
insertPostScript = _provider.GetInsertPostScript(primaryKeyName, tableName);
}
cmd.CommandText = string.Concat(
$"{insertPreamble}",
$"INSERT INTO {_provider.EscapeTableName(tableName)} ({(string.Join(",", names.ToArray()))})",
$"{outputClause}",
$" VALUES ({(string.Join(",", values.ToArray()))})",
$"{insertPostScript}"
) ;
if (!autoIncrement)
{....
这将更改命令以使用命名的TABLE变量DECLAREd在&#39; Preamble&#39;中,由OUTPUT子句填充并在&#39; PostScript&#39;中选择。