【问题标题】:PetaPoco insert fails on table with triggerPetaPoco 在带触发器的表上插入失败
【发布时间】:2013-12-20 07:28:30
【问题描述】:

我们使用 PetaPoco 作为 SQL 2008 数据库的数据访问工具。尝试在附加了触发器的表上插入/更新行时出现问题。

我们正在使用 PetaPoco 的 db.Save(object);

显示的错误是: 如果 DML 语句的目标表 'the_table_with_a_trigger' 包含没有 INTO 子句的 OUTPUT 子句,则该语句不能有任何启用的触发器。

我们如何使用 PetaPoco 在有触发器的表上插入/更新数据?

【问题讨论】:

  • 您认为需要该数据库和/或您的代码来帮助您吗?
  • 我不这么认为。对于带有触发器的表,这似乎是 PetaPoco 内部的一个问题。我猜 PetaPoco 创建插入命令的方式与触发器冲突。我希望有 PetaPoco 经验的人可以解释解决方法或解决方法。

标签: sql-server petapoco


【解决方案1】:

PetaPoco 仅在 Oracle DB 中创建一个 OUTPUT 参数来获取新的 AutoIncrement ID。

在 Sql Server 的情况下,当您标记问题时,如果表具有 AutoIncrement 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;

尝试关闭 AutoIncrement ID 并手动设置以查看问题是否消失

【讨论】:

    【解决方案2】:

    感谢@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);

    这会从 SQL 插入语句中删除“OUTPUT”。现在,插入将发生在带有触发器的表上。但是,现在 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 中,但未被使用)将允许插入语句检索身份。

    【讨论】:

      【解决方案3】:

      我认为cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;"; 会更好。 @@IDENTITY 可以给你一个在触发器中生成的 id,而不是你的语句。

      【讨论】:

        【解决方案4】:

        因为我确定我不会是最后一个遇到这种情况的人......

        我在新项目中采用了 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 变量,在“Preamble”中声明,由 OUTPUT 子句填充并在“PostScript”中选择。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-26
          • 1970-01-01
          相关资源
          最近更新 更多