I am dealing with this problem: I would like to have autogenerated identity in my table which is of type int
But, I would like to be able to explicitly set the identity. Now the real challenge is that this stuff is going through Entity Framework. I have my database with a IDENTITY(1,1) column, and IDENTITY_INSERT set to ON.
And whenever the Id is 0 (not specified) in newly created object, it inserts the very same 0. Any help appreciated, except offers to reconsider architecture (I will do that in any other case if this attempt fails).
And all this must work either on SQL CE, and SQL Server.
If you tell EF the primary key is database generated then it will not pass the id to the insert sql. You need to pass the ID so go with DatabaseGenerated.None.
But you want it to be an IDENTITY, so make it one in a migration script. You could change the CREATETABLE statement, adding identity: true to the column specification, or you can modify the table by running sql using the Sql() method
Now you need to modify the actual sql run during insert. The only way to do that is configure your model to use stored procedures then modify the sql generated in the Up migration for the insert procedures:
CREATE PROCEDURE [dbo].[My_Insert]
#Id int,
--ETC
AS
BEGIN
IF(Id > 0) SET IDENTITY_INSERT ON
INSERT --ETC
IF(Id > 0) THEN BEGIN
SET IDENTITY_INSERT OFF
SELECT Id
ELSE
SELECT SCOPE_IDENTITY() AS Id
END
END
Related
My application supports SQL Server and Compact edition. I use EntityFramework as the data access layer. To allow the users to go from Compact to SQL Server and back to Compact, I have a method to copy the data from one database to the other. This works fine from Compact to SQL Server, but when I use the same code to copy from SQL Server to Compact I get exceptions telling me that I cannot add a row with duplicate Id after an object was added to the EF data context and calling context.SaveChanges().
Here is how I copy the tables:
I let EF create the destination database
Open a SqlConnection(SqlCeConnection to source DB and destination DB
Foreach table I:
On the destination database SET IDENTITY_INSERT [Tablename] ON
Copy the table row by row using Sql inserts, including the ID's
On the destination database SET IDENTITY_INSERT [Tablename] OFF
So after I copy the database from SQL Server to Compact and using it with Entity Framework I get the above mentioned exception. So it looks like SET IDENTITY_INSERT ON works (because the data gets into the Compact database) but SET IDENTITY_INSERT OFF does not work on SQL Compact.
Does anyone has similar experiences and a solution for me?
If you are getting an error that it is trying to add a duplicate key, then the issue you are having doesn't have anything to do with the IDENTITY_INSERT setting. The identity insert only persists during your session.
What is probably happening is that you have inserted key values that are above the current identity seed values. You can reseed your table's identity seed with this command in SQL Server:
DBCC CHECKIDENT (MyTable, reseed)
You can execute this in Entity Framwork with:
context.Database.ExecuteSqlCommand("DBCC CHECKIDENT(MyTable, reseed)")
This will reset the seed to start numbering after the last value used in the table. Run this statement after populating every destination table.
Ok, ErikEJ tip brought me to the right direction. The final resolution was to
a) get the current max Id
SELECT MAX(Id) FROM TABLENAME
b) Reseeding but with MAX + 1
ALTER TABLE [TABLENAME] ALTER COLUMN [Id] IDENTITY (<MAX + 1>,1)
I have a migration script with the following statement:
ALTER TABLE [Tasks] ALTER COLUMN [SortOrder] int NOT NULL
What will happen if I run that twice? Will it change anything the second time? MS SQL Management Studio just reports "Command(s) completed successfully", but with no details on whether they actually did anything.
If it's not already idempotent, how do I make it so?
I would say that second time, SQL Server checks metadata and do nothing because nothing has changed.
But if you don't like possibility of multiple execution you can add simple condition to your script:
CREATE TABLE Tasks(SortOrder VARCHAR(100));
IF NOT EXISTS (SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE [TABLE_NAME] = 'Tasks'
AND [COLUMN_NAME] = 'SortOrder'
AND IS_NULLABLE = 'NO'
AND DATA_TYPE = 'INT')
BEGIN
ALTER TABLE [Tasks] ALTER COLUMN [SortOrder] INT NOT NULL
END
SqlFiddleDemo
When you execute it the second time, the query gets executed but since the table is already altered, there is no effect. So it makes no effect on the table.
No change is there when the script executes twice.
Here is a good MSDN read about: Inside ALTER TABLE
Let's look at what SQL Server does internally when performing an ALTER
TABLE command. SQL Server can carry out an ALTER TABLE command in any
of three ways:
SQL Server might need to change only metadata.
SQL Server might need to examine all the existing data to make sure
it's compatible with the change but then change only metadata.
SQL Server might need to physically change every row.
I have a stored procedure that does an insert of a row like this:
CREATE PROCEDURE dbo.sp_add_test
#CreatedBy NVARCHAR (128),
#TestId INT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.Test (
CreatedDate,
Title,
ParentTestId,
)
SELECT
#CreatedDate
Title,
#TestId
FROM Test
WHERE TestId = #TestId;
SELECT * from Test
WHERE TestId = #TestId
AND CreatedDate = #CreatedDate;
END
When inserted a new identity value will be generated for the primary key. As soon as the insert is completed I then do a select from that table.
Can someone tell me if there is another way I can do this? The reason I do a second select is that I need to get a value for the new TestId which is an identity column.
I am not familiar with the way SQL Server caches data. Does it cache recently used rows in the same way as Oracle does or will it go to the disk to get the row it just inserted?
In SQL Server, the right way to do this is with the OUTPUT clause. The documentation is here.
As far as I know, SQL Azure supports the OUTPUT clause.
As for your question, when a database commits as page (that is, when the insert is completed), the page often remains in memory. Typically, the "commit" is a log operation, so the data page remains in memory. An immediate access to the page should be fast, in the sense that it doesn't require a disk access. But the OUTPUT clause is the right approach in SQL Server.
I am using
exec sp_generate_inserts 'TABLENAME'
to copy all the records of one table from our test server to live server.
Do we have to create the table first before using this command?????
I tried to give this command and it gives me an error - Invalid Object name.
I tried to create the table first and then to give this command. But in that case I have an identity column in the table and it gives an error..... Cannot insert identity value .
I know that the identity insert is set to off. Do I have to turn it on and try....How can turn it on.....And also could you please let me know if we have an alternative way of doing it.
Thanks
YES of course you have to create the table first before inserting data into it....
And if you want to insert specific values into an IDENTITY column, you can use
SET IDENTITY_INSERT (your table name) ON
-- do your INSERTs here
SET IDENTITY_INSERT (your table name) OFF
I've just moved a database from a SQL 2000 instance to a SQL 2008 instance and have encountered an odd problem which appears to be related to IDENTITY columns and stored procedures.
I have a number of stored procedures in the database along the lines of this
create procedure usp_add_something #somethingId int, #somethingName nvarchar(100)
with encryption
as
-- If there's an ID then update the record
if #somethingId <> -1 begin
UPDATE something SET somethingName = #somethingName
end else begin
-- Add a new record
INSERT INTO something ( somethingName ) VALUES ( #somethingName )
end
go
These are all created as ENCRYPTED stored procedures. The id column (e.g. somethingId in this example) is an IDENTITY(1,1) with a PRIMARY KEY on it, and there are lots of rows in these tables.
Upon restoring onto the SQL 2008 instance a lot of my database seems to be working fine, but calls like
exec usp_add_something #somethingId = -1, #somethingName = 'A Name'
result in an error like this:
Violation of PRIMARY KEY constraint 'Something_PK'. Cannot insert duplicate key in object 'dbo.something'.
It seems that something is messed up that either causes SQL Server to not allocate the next IDENTITY correctly...or something like that. This is very odd!
I'm able to INSERT into the table directly without specifying the id column and it allocates an id just fine for the identity column.
There are no records with somethingId = -1 ... not that that should make any difference.
If I drop and recreate the procedure the problem goes away. But I have lots of these procedures so don't really want to do that in case I miss some or there is a customized procedure in the database that I overwrite.
Does anyone know of any known issues to do with this? (and a solution ideally!)
Is there a different way I should be moving my sql 2000 database to the sql 2008 instance? e.g. is it likely that Detach and Attach would behave differently?
I've tried recompiling the procedure using sp_recompile 'usp_add_something' but that didn't solve the problem, so I can't simply call that on all procedures.
thanks for any help
R
(cross-posted here)
If the problem is an improperly set identity seed, you can reset a table this way:
DBCC CHECKIDENT (TableName, RESEED, 0);
DBCC CHECKIDENT (TableName, RESEED);
This will automatically find the highest value in the table and set the seed appropriately so you don't have to do a SELECT Max() query. Now fixing the table can be done in automation, without dynamic SQL or manual script writing.
But you said you can insert to the table directly without a problem, so it's probably not the issue. But I wanted to post to set the record straight about the easy way to reset the identity seed.
Note: if your table's increment is negative, or you in the past reset the seed to use up all negative numbers starting at the lowest after consuming all the positive numbers, all bets are off. Especially in the latter case (having a positive increment, but you are using identity values lower than others already in the table), then you do not want to run DBCC CHECKIDENT without specifying NORESEED ever. Because just DBCC CHECKIDENT (TableName); will screw up your identity value. You must use DBCC CHECKIDENT (TableName, NORESEED). Fun times will ensue if you forget this. :)
First, check the maximum ID from your table:
select max(id_column) from YourTable
Then, check the current identity seed:
select ident_seed('YourTable')
If the current seed is lower than the maximum, reseed the table with dbcc checkident:
DBCC CHECKIDENT (YourTable, RESEED, 42)
Where 42 is the current maximum.
Demonstration code for how this can go wrong:
create table YourTable (id int identity primary key, name varchar(25))
DBCC CHECKIDENT (YourTable, RESEED, 42)
insert into YourTable (name) values ('Zaphod Beeblebrox')
DBCC CHECKIDENT (YourTable, RESEED, 41)
insert into YourTable (name) values ('Ford Prefect') --> Violation of PRIMARY KEY
I tried and was unable to replicate this on another server.
However, on my Live servers I dropped the problem database from sql 2008 and recreated it using a detach and reattach and this worked fine, without these PRIMARY KEY VIOLATION errors.
Since I wanted to keep the original database live, in fact my exact steps were:
back up sourceDb and restore as sourceDbCopy on the same instance
take sourceDbCopy offline
move the sourceDbCopy files to the new server
attach the database
rename the database to the original name
If recreating the procedures helps, here's an easy way to generate a recreation script:
Right click database -> Tasks -> Generate scripts
On page 2 ("Choose Objects") select the stored procedures
On page 3 ("set scripting options") choose Advanced -> Script DROP and CREATE and set it to Script DROP and CREATE.
Save the script somewhere and run it