DataTable identity column not set after DataAdapter.Update/Refresh on table with "instead of"-trigger (SqlServer 2005) - sql-server-2005

Within our unit tests we use plain ADO.NET (DataTable, DataAdapter) for preparing the database resp. checking the results, while the tested components themselves run under NHibernate 2.1. .NET version is 3.5, SqlServer version is 2005.
The database tables have identity columns as primary keys. Some tables apply instead-of-insert/update triggers (this is due to backward compatibility, nothing I can change). The triggers generally work like this:
create trigger dbo.emp_insert
on dbo.emp
instead of insert
as
begin
set nocount on
insert into emp ...
select ##identity
end
The insert statement issued by the ADO.NET DataAdapter (generated on-the-fly by a thin ADO.NET wrapper) tries to retrieve the identity value back into the DataRow:
exec sp_executesql N'
insert into emp (...) values (...);
select id, ... from emp where id = ##identity
'
But the DataRow's id-Column is still 0. When I remove the trigger temporarily, it works fine - the id-Column then holds the identity value set by the database.
NHibernate on the other hand uses this kind of insert statement:
exec sp_executesql N'
insert into emp (...) values (...);
select scope_identity()
'
This works, the NHibernate POCO has its id property correctly set right after flushing. Which seems a little bit counter-intuitive to me, as I expected the trigger to run in a different scope, hence ##identity should be a better fit than scope_identity().
So I thought no problem, I will apply scope_identity() instead of ##identity under ADO.NET as well. But this has no effect, the DataRow value is still not updated accordingly.
And now for the best part: When I copy and paste those two statements from SqlServer profiler into a Management Studio query (that is including "exec sp_executesql"), and run them there, the results seem to be inverse! There the ADO.NET version works, and the NHibernate version doesn't (select scope_identity() returns null). I tried several times to verify, but to no avail. Well, actually that was what I would have expected - ##identity to be OK, and scope_identity() to fail.
Of course invoking it in Management Studio just shows the resultset coming from the database, whatever happens inside NHibernate and ADO.NET is another topic. Also, several session properties defined by T-SQL SET are different in the two scenarios (Management Studio query vs. application at runtime)
This is a real puzzle to me. I would be happy about any insights on that. Thank you!

Found it. The identity value actually was transmitted into the DataTable, just not in the column I expected. Instead of using existing column "id", ADO.NET created a new column "Column1".
Reason is this line at the end of the instead-of trigger:
select ##identity
Unfortunately, NHibernate seems to require "select ##identity" at the end of instead-of triggers (this was mentioned in a Hibernate forum posting, and I verified it again now - it is indeed necessary). But I can go along from here (adapting NHibernate dialect is one possibility)...

Related

SQL Server 17, database migration, function for setting identity in primary key

I have two twin databases and want to migrate one into another. For that I would like to have a written function so that primary key can be set by utils function. I did already by using UI in SQL Server, but that is tiresome, as there are many tables to repeat that process. Utils function would be setting identity before migrating table and then removing it after job is done.
For example:
insert into TABLE
(colum_names)
select column_names
from TABLE
Before and after I would like to set and remove identity automatically stead doing this:
I know there are ways around this with recreating tables, but for obvious reasons(query time, server load) I do not want to do that :)
If I'm not misunderstanding, you are using an external utility to perform this migration. In that case, you can execute the SET IDENTITY_INSERT [dbo].[YourTable] ON; statement in a command prior to your migration steps.
For example, with C#:
using(SqlConnection myConn= new SqlConnection(conn))
{
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "SET IDENTITY_INSERT [dbo].[YourTable] ON;";
//Migration steps here...
cmd.CommandText += "SET IDENTITY_INSERT [dbo].[YourTable] OFF;";
}
EDIT:
The below is actually incorrect! I overlooked that IDENTITY_INSERT can only be set for a SINGLE table per SESSION. See MS documentation
As per a the commented suggestion by #benjamin moskovits to use the undocumented sp_msforeachtable, while this would work to SET IDENTITY_INSERT for each table in your database, this approach is nevertheless a bit of a sledgehammer which may have unintended consequences depending on the specifics of your migration pattern.
exec sp_msforeachtable 'SET IDENTITY_INSERT ? ON;'
If you want to do something to each table in a database you can use the 'unsupported' but very widely used and available in almost every version of SQL Server, sp_msforeachtable to execute something....
exec sp_msforeachtable 'select count(*) ''?'' from ?'
will give you a list of every table in a database and the number of rows it contains.

What is the DB2 equivalent of SQL Server's SET NOCOUNT ON?

What is the DB2 equivalent of SQL Server's SET NOCOUNT ON?
"From the SQL Server documentation:
SET NOCOUNT ON... Stops the message that shows the count of the number of rows affected by a Transact-SQL statement or stored procedure from being returned as part of the result set...
For stored procedures that contain several statements that do not return much actual data, or for procedures that contain Transact-SQL loops, setting SET NOCOUNT to ON can provide a significant performance boost, because network traffic is greatly reduced."
my problem is if I update a row in a table, a trigger runs that update another
row in a different table.
In Hibernate I get this error: "Batch update returned unexpected row
count from update; actual row count: 2; expected: 1".
I think because of the trigger DB2 returns 2 instead of 1, what
is correct. However, is there any way to make DB2 to return 1
without removing the trigger or can I disable the check in Hibernate?
How to handle this issue?
Can anyone plz tell "Set NoCount on"(sql server) equivalent in db2?
There is no equivalent to SET NOCOUNT in DB2 because DB2 does not produce any informational messages after a DML statement has completed successfully. Instead, the DB2 driver stores that type of information in a local, connection-specific data structure called the SQL communications area (SQLCA). It is up to the application (or whatever database framework or API the application is using) to decide which SQLCA variables to examine after executing each statement.
In your case, your application has delegated its database interaction to Hibernate, which compares the number of affected rows reported by DB2 in the SQLCA with the number of rows Hibernate expected its UPDATE statement to change. Since Hibernate isn't aware of the AFTER UPDATE trigger you created, it expects the update statement to affect only one row, but the SQLCA shows that two rows were updated (one by Hibernate's update statement, and one by the AFTER UPDATE trigger on that table), so Hibernate throws an exception to complain about the discrepancy.
This leaves you with two options:
Drop the trigger from that table and instead define an equivalent followup action in Hibernate. This is not an ideal solution if other applications that don't use Hibernate are also updating the table in question, but that's the sort of decision a team gets to make when they inflict Hibernate on a database.
Keep the AFTER UPDATE trigger where it is in DB2, and examine your options for defining Hibernate object mappings to determine if there's a way to at least temporarily disable Hibernate's row count verification logic. One approach that looks particularly encouraging is to specify the ResultCheckStyle.NONE option as part of a custom #SQLUpdate annotation.
For SQL Server and Sybase, there appears to be a third option: Hide the activity of an AFTER UPDATE trigger from Hibernate by activating SET NOCOUNT ON within the trigger. Unfortunately, there is no equivalent in DB2 (or Oracle, for that matter) that allows an application to selectively skip certain activities when tallying the number of affected rows.

simple SSIS help

i need to create a simple package that will:
insert a data to table A
get the scope_identity()
insert multiple rows to table B with the id from table A
i created one, but it only does bulk insert, so i want to redo it again.
any help would be appreciated.
Ah, in this case I would probably not use a lookup but an "Execute SQL"-task.
You can have this execute an "arbitrary" SQL statement.Getting the last inserted identity value can be a bit tricky though. If you are not concerned about concurrent inserts not controlled by you a simple "MAX(...)" might do. Something along the lines of
SELECT max(<column_name>) FROM <A>
Otherwise it gets ab bit more involved. Look for an SQL statement that does what you want (this has nothing to do with SSIS, just with SQL). As I assume you are using MSSQL you might have a look at "SQL SERVER – ##IDENTITY vs SCOPE_IDENTITY() vs IDENT_CURRENT – Retrieve Last Inserted Identity of Record" for example.
Once you have your SQL plug it into an "Execute SQL" task and save its return value into a variable. See "How to load a new table with the value of a variable from SSIS package?" for hints how to do this (sorry, I can't find a perfect example right now...). Maybe also either "How to use OUTPUT parameters with SSIS Execute SQL Task" or "Technet - SSIS Execute SQL Task".
And bear in mind: you do not "use <whatever component> from the destination component" but before it and store the result (here: current identity value) in a variable you can then use later.

multiple select statements in single ODBCdataAdapter

I am trying to use an ODBCdataadapter in C# to run a query which needs to select some data into a temporary table as a preliminary step. However, this initial select statement is causing the query to terminate so that data gets put into the temp table but I can't run the second query to get it out. I have determined that the problem is the presence of two select statements in a single dataadapter query. That is to say the following code only runs the first select:
select 1
select whatever from wherever
When I run my query directly through SQL Server Management Studio it works fine. Has anyone encountered this sort of issue before? I have tried the exact same query previously on similar databases using the same C# code (only the connection string is different) and had no problems.
Before you ask, the temp table is helpful because otherwise I would be running a whole lot of inner select statements which would bog down the database.
Assuming you're executing a Command that's command type is CommandText you need a ; to separate the statements.
select 1;
select whatever from wherever;
You might also want to consider using a Stored Procedure if possible. You should also use the SQL client objects instead of the ODBC client. That way you can take advantage of additional methods that aren't available otherwise. You're supposed to get better perf as well.
If you need to support multiple Databases you can just use the DataAdapter class and use a Factory o create the concrete types. This gives you the benefits of using the native drivers without being tied to a specific backend. ORMS that support multiple back ends typically do this. The Enterprise Library Data Access Application Block while not an ORM does this as well.
Unfortunately I do not have write access to the DB as my organization has been contracted just to extract information to a data warehouse. The program is one generalized for use on multiple systems which is why we went with ODBC. I suppose it would not be terrible to rewrite it using SQL Management Objects.
ODBC Connection requires a single select statement and its retrieval from SQL Server.
If any such functionality is required, a Hack can do the purpose
use the query
SET NOCOUNT ON
at the top of your select statement.
When SET NOCOUNT is ON, the count (indicating the number of rows affected by a Transact-SQL statement) is not returned.
When SET NOCOUNT is OFF, the count is returned. It is used with any SELECT, INSERT, UPDATE, DELETE statement.
The setting of SET NOCOUNT is set at execute or run time and not at parse time.
SET NOCOUNT ON mainly improves stored procedure (SP) performance.
Syntax:
SET NOCOUNT { ON | OFF }

Why would ##IDENTITY or SCOPE_IDENTITY() be DBNull?

(resolved: see bottom)
I have the following code snippet:
Protected Sub SqlDataSource1_Inserted(ByVal sender As Object,
ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
Handles SqlDataSource1.Inserted
affected = CInt(DirectCast(e.Command.Parameters("#affected"), IDbDataParameter).Value)
newID = CInt(DirectCast(e.Command.Parameters("#newID"), IDbDataParameter).Value)
End Sub
Where #newID is defined like this in the SQL string:
"INSERT INTO x(a,b,c) VALUES (#a,#b,#c); SELECT #affected = ##rowcount, #newID = SCOPE_IDENTITY();
The parameters are defined using ASP.NET as follows:
The strange thing about it is that this works 90% of the time, but every once and a while it throws an InvalidCastException saying that "Conversion from type 'DBNull' to type 'Integer' is not valid." Any ideas on what could be causing this value to be null? I don't have any triggers set on the table, and the only thing my query is doing is running a plain insert into 1 table.
Edit: Based on the suggestions here, I added an affected parameter. I set a breakpoint, and affected = 1 but I still got the exception. However, I then figured out that I had SELECT #newID before SELECT #affected. I switched the order, and now #affected = 0. So it appears to be a problem with my insert statement after all. Thanks for your help!
From MSDN
After an INSERT, SELECT INTO, or bulk
copy statement is completed,
##IDENTITY contains the last identity
value that is generated by the
statement. If the statement did not
affect any tables with identity
columns, ##IDENTITY returns NULL.
Do you have a check to see if the insert has succeeded? It may be possible that the record you are trying to insert fails to insert anything for some reason, therefore the id is null.
Are there any triggers set up on the table? You may be retrieving the id from a trigger procedure using ##IDENTITY
I would suggest either using SCOPE_IDENTITY() or an output parameter to get the id
You should use SCOPE_IDENTITY() as oppose to ##IDENTITY in 99.9% of cases. It is very rare you will have a situation that requires something other than SCOPE_IDENTITY()
##IDENTITY returns the last IDENTITY
value produced on a connection,
regardless of the table that produced
the value, and regardless of the scope
of the statement that produced the
value. If you have a trigger on a
table that causes an identity to be
created in another table, you will get
the identity that was created last,
even if it was the trigger that
created it.
SCOPE_IDENTITY() returns the last IDENTITY value produced on a
connection and by a statement in the
same scope, regardless of the table
that produced the value.
SCOPE_IDENTITY(), like ##IDENTITY,
will return the last identity value
created in the current session, but it
will also limit it to your current
scope as well.
Are you sure there are no auditing triggers on that table (perhaps added by your DBA)?
Various possibilities:
INSERT does not insert anything (as
erikkallen said)
your statement (not shown) inserts
to various tables, the last one of
which does not have an identity
column
the INSERT fires a trigger which
inserts to a table without an
identity column - try
SCOPE_IDENTITY() instead
You might want to perform a check to see if ##ROWCOUNT > 0.
Also there were known problems in past versions of SQL Server with triggers impacting the survivability of ##IDENTITY. Do you have any triggers?
Another time when this occurred, changing
<asp:Parameter Name="newID" Direction="Output" Size="4" />
to
<asp:Parameter Name="newID" Direction="Output" Type="Int32" Size="4" />
fixed the problem for me. It appeared that my host turned on some requirements for more explicit declaration.
What is the query text... part? An INSERT, I guess.
Could it be an insert ... select which didn't find any rows?
You may receive incorrect values when using SCOPE_IDENTITY() and ##IDENTITY. Specifically when the query ends up being executed as a parallel query:
http://support.microsoft.com/default.aspx?scid=kb;en-US;2019779