Parametrize query VS SCOPE_IDENTITY() - WTF?

Parametrize query VS SCOPE_IDENTITY() - WTF?

Old forum URL: forums.lhotka.net/forums/t/1356.aspx


guyroch posted on Thursday, September 28, 2006

I'm having some problem getting the value of SCOPE_IDENTITY() immediatly following a paremetrize query, but works fine after a straight hard coded command.


SAMPLE #1

// Insert command hard coded parameter
IDbCommand dbCommand = this.DataFactory.CreateCommand();
dbCommand.CommandType = CommandType.Text;
dbCommand.CommandText = string.Format(
    "INSERT INTO [Severity] ([Descriptor]) VALUES ('{0}')",
    ColumnNames.Descriptor);

// Execute the parametrize query
dbCommand.ExecuteNonQuery();

// Get the SCOPE_IDENTITY()
IDbCommand dbScopeCommand = this.DataFactory.CreateCommand();
dbScopeCommand.CommandType = CommandType.Text;
dbScopeCommand.CommandText = "SELECT SCOPE_IDENTITY()";

this.SeverityId = int.Parse(dbScopeCommand.ExecuteScalar().ToString());


----------------------------------------------------------------------

SAMPLE #2

// Insert command using a parametrized query
IDbCommand dbCommand = this.DataFactory.CreateCommand();
dbCommand.CommandType = CommandType.Text;
dbCommand.CommandText = "INSERT INTO [Severity] ([Descriptor]) VALUES (@p1)";

// Add the parameter
IDbDataParameter p = dbCommand.CreateParameter();
p.ParameterName = "@p1";
p.DbType = DbType.String;
p.Value = this.Descriptor;
p.Direction = ParameterDirection.Input;
dbCommand.Parameters.Add(p);

// Execute the parametrize query
dbCommand.ExecuteNonQuery();

// Get the SCOPE_IDENTITY()
IDbCommand dbScopeCommand = this.DataFactory.CreateCommand();
dbScopeCommand.CommandType = CommandType.Text;
dbScopeCommand.CommandText = "SELECT SCOPE_IDENTITY()";

this.SeverityId = int.Parse(dbScopeCommand.ExecuteScalar().ToString());


----------------------------------------------------------------------


In both cases the insert command runs with no problem BUT the SCOPE_IDENTITY() command will fail and will throw an "Input string was not in a correct format" with I run SAMPLE #2 using the parametrize query.  WTF ???  The value returned in sample #2 is always 'null'.


Csla.DataPortalException: DataPortal.Update failed ---> Csla.Server.CallMethodException: DataPortal_Insert method call failed ---> System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
   at System.Int32.Parse(String s)
  
  
Any ideas?

guyroch replied on Thursday, September 28, 2006

BTW, if I use @@IDENTITY instead of SCOPE_IDENTITY() it works.  But If I use @@IDENTITY I will run into other issues down the road with insert triggers an al - so SCOPE_IDENTITY() is by far the right solution.

Thanks

guyroch replied on Thursday, September 28, 2006

Okay, this one works but not very elegant in my opinion.

// Insert command using a parametrized query
IDbCommand dbCommand = this.DataFactory.CreateCommand();
dbCommand.CommandType = CommandType.Text;
dbCommand.CommandText = "INSERT INTO [Severity] ([Descriptor]) VALUES (@p1); SELECT SCOPE_IDENTITY()";

// Add the parameter
IDbDataParameter p = dbCommand.CreateParameter();
p.ParameterName = "@p1";
p.DbType = DbType.String;
p.Value = this.Descriptor;
p.Direction = ParameterDirection.Input;
dbCommand.Parameters.Add(p);

// Execute the parametrize query
dbCommand.ExecuteNonQuery();

this.SeverityId = int.Parse(dbCommand.ExecuteScalar().ToString());

-----------------------

What I did is very simple, i've combined both command into so that both are ran within the same scope context.

Is there a better way retain the scope context between 2 individual IDbCommand?

 

ajj3085 replied on Friday, September 29, 2006

Is there a reason you're not using a stored procedure?  I'm not sure why the commands in #2 aren't running in the same scope; perhaps parametizing things creates another context?  If you can though I'd recommend putting the insert into an sp which has an output parameter through which you can pass the value from scope_identity().

guyroch replied on Friday, September 29, 2006

Thanks guys for your input here - it is appreciated.

ajj3085:
Is there a reason you're not using a stored procedure?  I'm not sure why the commands in #2 aren't running in the same scope; perhaps parametizing things creates another context?  If you can though I'd recommend putting the insert into an sp which has an output parameter through which you can pass the value from scope_identity().

The reason I'm not using store proc is that I'm currently writting a DAL that will enable our CSLA based application to connect to more than one database like SQL Server, Oracle, and Firebird just to name a few.  I'm a big fan of code generation and the DAL layer is generated in .Net code only - so no store procs here.  While this approach will bring forward some issues like the one I've just experienced, having the entire DAL generated in .Net code will allow for strongly typed data entity that maps back top the database tables and columns.  Thus, having a compile error instead of a runtime error when the data schema is altered due to new features or other reasons.

ajj3085 replied on Monday, October 02, 2006

GuyRoch,

Understand your reasoning, I have my own DAL as well.  However I designed my DAL to always use stored procedures when connecting via Sql Server.  Basically my Sql server provider assumes certain things.

For example, for the Employee table, the DAL assumes a view exists called vEmployee, which is just a select * from employee.  It also assumes procedures exists, apEmployeeInsert, apEmployeeUpdate, apEmployeeDelete.  The procs do just what you expect.

The nice part is that once I forced these rules I was able to create an entity generator; it looks at a table, and creates the view, procs and .cs files for me, all ready to go. 

There's no reason you can't have different rules for each provider.  I successfully created a DAL that worked with SPs when connecting to Sql Server, but used direct ad-hoc queries for a Filemaker database (which supports only the most basic sql).

I know you may not be able to change direction for whatever reason, but it may be worth thinking about because of the code gen advantages.

Andy

guyroch replied on Monday, October 02, 2006

Andy,

Sounds good, but what do you do when you encounter a data type not supported in both databases.  For example a timestamp data type exists in SQL Server but does not not exists is Oracle.  What did you do to get around this issue - or was it not an issue for you guys.

ajj3085 replied on Tuesday, October 03, 2006

Guy,

Aren't timestamps read only and come back as a byte[] in .net?  What particular problem are you having?

JoeFallon1 replied on Wednesday, November 01, 2006

I got an answer to this question from Erland Sommarskog - a long time SQL Server MVP.

<quote>

Apparently, you send an unparameterised command
with ExecuteNonQuery, and in that case it will work. But if you would
starting using parameters, it would break.

scope_idenity() returns the most recently generated IDENTITY value in
the current scope. "scope" is a "stored procedure", "block of dynamic
SQL" etc. And the top level of a connection is also a scope.

As long as you don't use parameters, ADO .Net will send bare SQL commands
to SQL Server and they will thus execute in the top-level scope. But
if you use parameterised commands, ADO .Net implements this by calling
sp_executesql, and thus the INSERT commands excutes in a lower-level
scope, and thuse the call to scope_idenity will not return any value.

From this it may sound that using parameters with ADO .Net is a bad
idea, but in fact it is a very good idea. Parameterised commands
increases the chances for cache reuse, and decreases the risk for
the cache being cluttered. Parameterised commands is also the basic
protection against SQL injection.

</quote>

So you can send sequential commands across the same connection and it will work correctly - and a transaction uses the same connection so it works too.

Joe

 

guyroch replied on Wednesday, November 01, 2006

Thanks for posting this Joe.  This is valuable information.

JHurrell replied on Friday, September 29, 2006

I don't think you'll ever get SCOPE_IDENTITY to work across two different executed commands. A scope is a stored procedure, trigger, function or batch and you're not within one of any of them.

Your second example (INSERT INTO [Severity] ([Descriptor]) VALUES (@p1); SELECT SCOPE_IDENTITY()) works because they are indeed in the same batch.

If you don't like the batched pattern, you're only recourse is to create a procedure.

There is one other thing you can do but I wouldn't recommend it. You could use: SELECT IDENT_CURRENT('Severity') which will return the last IDENTITY set for the specified table, but you can't be sure that the last one was yours.

- John

Copyright (c) Marimer LLC