14

仅供参考:我在 dotnet 3.5 SP1 上运行

我试图在执行更新后(使用 SqlDataAdapter 和 SqlCommandBuilder)将标识列的值检索到我的数据集中。执行 SqlDataAdapter.Update(myDataset) 后,我希望能够读取 的自动分配值myDataset.tables(0).Rows(0)("ID"),但它是 System.DBNull(尽管插入了行)。

(注意:我不想显式地编写一个新的存储过程来做到这一点!)

一种经常发布的方法http://forums.asp.net/t/951025.aspx像这样修改 SqlDataAdapter.InsertCommand 和 UpdatedRowSource:

SqlDataAdapter.InsertCommand.CommandText += "; SELECT MyTableID = SCOPE_IDENTITY()"
InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord

显然,这似乎对过去的许多人有用,但对我不起作用。

另一种技术:http ://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=619031&SiteID= 1 对我也不起作用,因为在执行 SqlDataAdapter.Update 后,SqlDataAdapter.InsertCommand.Parameters 集合被重置为原始的(丢失了额外的添加参数)。

有谁知道这个问题的答案???

4

14 回答 14

10

这是我之前遇到的一个问题,错误似乎是当您调用 da.Update(ds); 时 插入命令的参数数组被重置为从您的命令生成器创建的初始列表,它会删除您为身份添加的输出参数。

解决方案是创建一个新的 dataAdapter 并复制命令,然后使用这个新的来执行您的 da.update(ds);

喜欢

SqlDataAdapter da = new SqlDataAdapter("select Top 0 " + GetTableSelectList(dt) + 
"FROM " + tableName,_sqlConnectString);
SqlCommandBuilder custCB = new SqlCommandBuilder(da);
custCB.QuotePrefix = "[";
custCB.QuoteSuffix = "]";
da.TableMappings.Add("Table", dt.TableName);

da.UpdateCommand = custCB.GetUpdateCommand();
da.InsertCommand = custCB.GetInsertCommand();
da.DeleteCommand = custCB.GetDeleteCommand();

da.InsertCommand.CommandText = String.Concat(da.InsertCommand.CommandText, 
"; SELECT ",GetTableSelectList(dt)," From ", tableName, 
" where ",pKeyName,"=SCOPE_IDENTITY()");

SqlParameter identParam = new SqlParameter("@Identity", SqlDbType.BigInt, 0, pKeyName);
identParam.Direction = ParameterDirection.Output;
da.InsertCommand.Parameters.Add(identParam);

da.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;

//new adaptor for performing the update                     
SqlDataAdapter daAutoNum = new SqlDataAdapter();
daAutoNum.DeleteCommand = da.DeleteCommand;
daAutoNum.InsertCommand = da.InsertCommand;
daAutoNum.UpdateCommand = da.UpdateCommand;

daAutoNum.Update(dt);
于 2011-09-30T16:44:41.453 回答
4

可以指示插入命令使用 UpdatedRowSource 属性使用输出参数或第一个返回的记录(或两者)更新插入的记录...

InsertCommand.UpdatedRowSource = UpdateRowSource.Both;

如果你想使用存储过程,你就完成了。但是您想使用原始命令(又名命令生成器的输出),它不允许 a) 输出参数或 b) 返回记录。为什么是这样?好吧,a)这就是您的 InsertCommand 的样子...

INSERT INTO [SomeTable] ([Name]) VALUES (@Name)

无法在命令中输入输出参数。那么b)呢?不幸的是,DataAdapter 通过调用命令 ExecuteNonQuery 方法来执行插入命令。这不会返回任何记录,因此适配器无法更新插入的记录。

所以你需要要么使用存储过程,要么放弃使用 DataAdapter。

于 2008-09-26T02:48:01.007 回答
4

对我有用的是配置 MissingSchemaAction:

SqlCommandBuilder commandBuilder = new SqlCommandBuilder(myDataAdapter);
myDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;

这使我可以在插入后检索主键(如果它是标识或自动编号)。

祝你好运。

于 2010-03-25T09:04:24.320 回答
4

我在这里找到了适合我的答案:http: //www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-ado-net/4933/Have-soln-but-need-understanding-return-IDENTITY-issue

有效的代码(来自网站 - 归因于 Girish)

//_dataCommand is an instance of SqlDataAdapter
//connection is an instance of ConnectionProvider which has a property called DBConnection of type SqlConnection
//_dataTable is an instance of DataTable

SqlCommandBuilder bldr = new SqlCommandBuilder(_dataCommand);
SqlCommand cmdInsert = new SqlCommand(bldr.GetInsertCommand().CommandText, connection.DBConnection);
cmdInsert.CommandText += ";Select SCOPE_IDENTITY() as id";

SqlParameter[] aParams = new
SqlParameter[bldr.GetInsertCommand().Parameters.Count];
bldr.GetInsertCommand().Parameters.CopyTo(aParams, 0);
bldr.GetInsertCommand().Parameters.Clear();

for(int i=0 ; i < aParams.Length; i++)
{
 cmdInsert.Parameters.Add(aParams[i]);
}

_dataCommand.InsertCommand = cmdInsert;
_dataCommand.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
_dataCommand.Update(_dataTable);
于 2011-08-31T10:19:06.460 回答
2

实际上,这对我有用:

SqlDataAdapter.InsertCommand.CommandText += "; SELECT MyTableID = SCOPE_IDENTITY()" InsertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;

干杯。

于 2010-01-08T15:47:18.667 回答
2

我有同样的问题。当我克隆由commandbuilder生成的命令时,它刚刚解决了。看起来即使您更改了 insertcommand 的 commandText,它也会不断获取由 Commandbuilder 生成的命令...这是我用来克隆命令的代码...

        private static void CloneBuilderCommand(System.Data.Common.DbCommand toClone,System.Data.Common.DbCommand repository)
    {
        repository.CommandText = toClone.CommandText;
        //Copying parameters
        if (toClone.Parameters.Count == 0) return;//No parameters to clone? go away!
        System.Data.Common.DbParameter[] parametersArray= new System.Data.Common.DbParameter[toClone.Parameters.Count];
        toClone.Parameters.CopyTo(parametersArray, 0);
        toClone.Parameters.Clear();//Removing association before link to the repository one
        repository.Parameters.AddRange(parametersArray);            
    }
于 2010-03-04T07:17:15.140 回答
2

对我来说,问题是代码的放置位置。

在 RowUpdating 事件处理程序中添加代码,如下所示:

void dataSet_RowUpdating(object sender, SqlRowUpdatingEventArgs e)
{
    if (e.StatementType == StatementType.Insert)
    {
        e.Command.CommandText += "; SELECT ID = SCOPE_IDENTITY()";
        e.Command.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
     }
}
于 2012-08-24T08:07:19.833 回答
2

如果您只想 (a) 将一条记录插入表中,(b) 使用 DataSet,并且 (c) 不使用存储过程,那么您可以按照以下步骤操作:

  1. 创建您的 dataAdapter,但在 select 语句中添加 WHERE 1=0,这样您就不必下载整个表 - 性能的可选步骤

  2. 使用范围标识选择语句和输出参数创建自定义 INSERT 语句。

  3. 进行正常处理,填充数据集,添加记录并保存表更新。

  4. 现在应该能够直接从参数中提取身份。

例子:

    '-- post a new entry and return the column number
    ' get the table stucture
    Dim ds As DataSet = New DataSet()
    Dim da As SqlDataAdapter = New SqlDataAdapter(String.Concat("SELECT * FROM [", fileRegisterSchemaName, "].[", fileRegisterTableName, "] WHERE 1=0"), sqlConnectionString)
    Dim cb As SqlCommandBuilder = New SqlCommandBuilder(da)

    ' since we want the identity column back (FileID), we need to write our own INSERT statement
    da.InsertCommand = New SqlCommand(String.Concat("INSERT INTO [", fileRegisterSchemaName, "].[", fileRegisterTableName, "] (FileName, [User], [Date], [Table]) VALUES (@FileName, @User, @Date, @Table); SELECT @FileID = SCOPE_IDENTITY();"))
    da.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
    With da.InsertCommand.Parameters
        .Add("@FileName", SqlDbType.VarChar, 1024, "FileName")
        .Add("@User", SqlDbType.VarChar, 24, "User")
        .Add("@Date", SqlDbType.DateTime, 0, "Date")
        .Add("@Table", SqlDbType.VarChar, 128, "FileName")
        ' allow the @FileID to be returned back to us
        .Add("@FileID", SqlDbType.Int, 0, "FileID")
        .Item("@FileID").Direction = ParameterDirection.Output
    End With

    ' copy the table structure from the server and create a reference to the table(dt)
    da.Fill(ds, fileRegisterTableName)
    Dim dt As DataTable = ds.Tables(fileRegisterTableName)

    ' add a new record
    Dim dr As DataRow = dt.NewRow()
    dr("FileName") = fileName
    dr("User") = String.Concat(Environment.UserDomainName, "\", Environment.UserName)
    dr("Date") = DateTime.Now()
    dr("Table") = targetTableName
    dt.Rows.Add(dr)

    ' save the new record
    da.Update(dt)

    ' return the FileID (Identity)
    Return da.InsertCommand.Parameters("@FileID").Value

但是,做与此相同的事情实在是太啰嗦了……

' add the file record
    Dim sqlCmd As SqlCommand = New SqlCommand(String.Concat("INSERT INTO [", fileRegisterSchemaName, "].[", fileRegisterTableName, "] (FileName, [User], [Date], [Table]) VALUES (@FileName, @User, @Date, @Table); SELECT SCOPE_IDENTITY();"), New SqlConnection(sqlConnectionString))
    With sqlCmd.Parameters
        .AddWithValue("@FileName", fileName)
        .AddWithValue("@User", String.Concat(Environment.UserDomainName, "\", Environment.UserName))
        .AddWithValue("@Date", DateTime.Now())
        .AddWithValue("@Table", targetTableName)
    End With
    sqlCmd.Connection.Open()
    Return sqlCmd.ExecuteScalar
于 2010-12-07T09:53:12.060 回答
1

如果这些其他方法对您不起作用,那么.Net 提供的工具(SqlDataAdapter)等并没有真正提供关于灵活性的其他很多东西。您通常需要将其提升到一个新的水平并开始手动做一些事情。存储过程将是继续使用 SqlDataAdapter 的一种方式。否则,您需要转移到另一个数据访问工具,因为 .Net 数据库具有限制,因为它们的设计很简单。如果您的模型不符合他们的愿景,您必须在某个点/级别推出自己的代码。

于 2008-09-26T01:54:24.397 回答
1

另一种仅使用命令对象的方法。

Dim sb As New StringBuilder
sb.Append(" Insert into ")
sb.Append(tbl)
sb.Append(" ")
sb.Append(cnames)
sb.Append(" values ")
sb.Append(cvals)
sb.Append(";Select SCOPE_IDENTITY() as id") 'add identity selection

Dim sql As String = sb.ToString

Dim cmd As System.Data.Common.DbCommand = connection.CreateCommand
cmd.Connection = connection
cmd.CommandText = sql
cmd.CommandType = CommandType.Text
cmd.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord

'retrieve the new identity value, and update the object
Dim dec as decimal = CType(cmd.ExecuteScalar, Decimal)
于 2011-09-02T06:52:45.243 回答
0

实际上,它更容易,不需要任何自定义命令。在数据集设计器中创建表适配器时,请在向导的高级选项中指定“刷新数据表”。这样,在您发出 dataadapter.update mydataset 之后,您会发现数据表中的识别列填充了数据库中的新值。

于 2015-03-17T20:30:39.243 回答
0

我的解决方案:

SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Mytable;" , sqlConnection);

SqlCommandBuilder cb = new SqlCommandBuilder(da);

da.DeleteCommand = (SqlCommand)cb.GetDeleteCommand().Clone();
da.UpdateCommand = (SqlCommand)cb.GetUpdateCommand().Clone();

SqlCommand insertCmd = (SqlCommand)cb.GetInsertCommand().Clone();

insertCmd.CommandText += ";SET @Id = SCOPE_IDENTITY();";
insertCmd.Parameters.Add("@Id", SqlDbType.Int, 0, "Id").Direction = ParameterDirection.Output;
da.InsertCommand = insertCmd;
da.InsertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;

cb.Dispose();
于 2016-11-15T16:27:51.957 回答
0

问题是在使用SqlDataAdapter.Update(DataSet)方法时未考虑您对InsertCommand所做的更改。完美的解决方案是克隆由CommandBuilder生成的InsertCommand

SqlCommandBuilder cb = new SqlCommandBuilder(da);
cb.ConflictOption = ConflictOption.OverwriteChanges;

da.InsertCommand = cb.GetInsertCommand().Clone();
da.InsertCommand.CommandText += "; SELECT idTable = SCOPE_IDENTITY()";
da.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;

da.Update(dt);
于 2021-05-26T15:19:22.807 回答
-1

您是否考虑过使用 LINQ?我知道这并不能解决您的实际问题,但是如果您使用的是 .NET 3.5,您真的应该尝试使用 LINQ。事实上,随着 Code First EntityFramework 的出现,我认为您可以轻松地选择 LINQ to SQL 或 EF 作为滚动您自己的 DAL 的相对轻量级的替代方案。

于 2008-10-01T21:47:25.973 回答