Reputation: 12002
I am trying to use SQL Server for my databases with my Laravel project. I was able to connect SQL Server with Laravel 5.2. However, when I try to seed data into the table I get this error
[Illuminate\Database\QueryException] SQLSTATE[23000]: [Microsoft][SQL Server Native Client 11.0][SQL Server]Cann ot insert explicit value for identity column in table 'surveys' when IDENTITY_INSERT is set to OFF. (SQL: insert into [surveys] ([id], [name]) values (10, 'Some Text'))
Note: I am trying to supply the identity value which is probably what is causing the problem.
While researching the SQL error, I learned that I need to execute the following queries.
Before seeding I need to execute
SET IDENTITY_INSERT surveys ON;
After the seeding I need to execute
SET IDENTITY_INSERT surveys OFF;
But I am not sure how can I execute these command using Laravel
How can I seed while supplying the value for the identity column without this issue?
UPDATED Here is my seeder
<?php
use Illuminate\Database\Seeder;
class FinalSurveyTS extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$myTable = 'surveys';
DB::statement('SET IDENTITY_INSERT ' . $myTable . ' ON');
DB::table($myTable)->truncate();
DB::table($myTable)->insert([
'id' => 10,
'name' => 'some name',
]);
DB::statement('SET IDENTITY_INSERT ' . $myTable . ' OFF');
}
}
Upvotes: 8
Views: 6658
Reputation: 11
If you're getting an Assertion Error, try this workaround:
DB::unprepared('SELECT 1; SET IDENTITY_INSERT test_table ON');
DB::table('articles')->insert(['id' => $request->input('article_id'), 'title' => $request->input('title'), 'body'=> $request->input('body')]);
DB::unprepared('SELECT 1; SET IDENTITY_INSERT test_table OFF');
DB::unprepared
will work, although in my case I was running into an issue running this on macOS when I had the Doctrine DBAL (Database Abstraction Layer) installed, which is a dependency of Laravel Nova. It turns out that DB::unprepared
would return false
for this statement, which worked fine except that the DBAL would cause an Assertion Error.
AssertionError
assert($result !== false)
at vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:54
I struggled with a number of approaches to work around this but ultimately what I found was that by adding a trivial SELECT before the SET IDENTITY_INSERT table_name ON
, it would return true
and the AssertionError would no longer be a problem.
Please let me know if there's a more elegant solution. It's possible that my particular issue was a driver quirk or DB setting, but I'm using the same Docker SQL Server 2019 configuration as on other environments that didn't have this issue.
Upvotes: 0
Reputation: 2222
I ended up creating the following methods to control the IDENTITY_INSERT
to ON/OFF for SQL table seeding process:
function setIdentityInsert($table, $onStatus = true)
{
$status = $onStatus ? 'ON' : 'OFF';
$this->sqlConnection->unprepared("SET IDENTITY_INSERT $table $status");
}
function insertTableData($table, $data)
{
return $this->sqlConnection->table($table)->insert($data);
}
function seedTable($table, $hasAutoIncrementPrimaryKey = false, $data = [])
{
if ($hasAutoIncrementPrimaryKey) {
$this->setIdentityInsert($table);
$response = $this->insertTableData($table, $data);
$this->setIdentityInsert($table, false);
return $response;
}
else {
return $this->insertTableData($table, $data);
}
}
Note: Generally, the table requires to have an auto-increment primary key to set Identity Insert to ON
, that's why I have the $hasAutoIncrementPrimaryKey
flag. Otherwise, seeding may throw an error as:
SQLSTATE[HY000]: General error: 544 Cannot insert explicit value for
identity column in table 'test_table_name' when IDENTITY_INSERT is set to
OFF. [544] (severity 16) [(null)]
Hope this helps!
Upvotes: 0
Reputation: 60058
For anyone finding this via Google - the new correct answer is to use unprepared()
like this:
DB::beginTransaction();
DB::unprepared('SET IDENTITY_INSERT test ON');
DB::table('test')->insert(['id' => 1, 'name' => 'example']);
DB::unprepared('SET IDENTITY_INSERT test OFF');
DB::commit();
As discussed on this issue thread: https://github.com/laravel/framework/issues/27778
Upvotes: 9
Reputation: 11
Any raw statement will be executed separately with a separate connection to the database.
Because of this, you should follow the way I did for the connection; Here is the sample code;
DB::transaction(function () use ($reader, $dbDriver, $tableName) {
if ($dbDriver == 'pgsql') DB::statement('ALTER TABLE ' . $tableName . ' DISABLE TRIGGER ALL');
elseif ($dbDriver == 'sqlsrv')
{
$pdo = DB::connection()->getPdo();
$pdo->setAttribute(PDO::SQLSRV_ATTR_DIRECT_QUERY, true);
DB::statement('SET IDENTITY_INSERT ' . $tableName . ' ON');
}
foreach ($reader as $row) DB::table($tableName)->insert([$row]);
if ($dbDriver == 'pgsql') DB::statement('ALTER TABLE ' . $tableName . ' ENABLE TRIGGER ALL');
elseif ($dbDriver == 'sqlsrv')
{
DB::statement('SET IDENTITY_INSERT ' . $tableName . ' OFF');
$pdo->setAttribute(PDO::SQLSRV_ATTR_DIRECT_QUERY, false);
}
});
Upvotes: 1
Reputation: 12002
Here is what I found.
Laravel does not support raw statement inside of a transaction.
Any raw statement will be executed separately with a separate connection to the database.
The means when executing
DB::statement('SET IDENTITY_INSERT surveys ON;');
DB::table(...)->insert(...);DB::table(...)->insert(...);DB::table(...)->insert(...);
DB::statement('SET IDENTITY_INSERT surveys ON;');
..
Laravel will process 3 different database connection. This means that the second statement will never be aware of the first or third transaction. There for the first line will have no affect what so ever in this case.
The work around to this problem "until Laravel adds support to the raw statement to the transaction" is to create insert statement as a raw query like so
DB::statement('
SET IDENTITY_INSERT surveys ON;
INERT INTO table(id, col1, col2)
VALUES(10, 'blah','blah'), (11, 'blah','blah'), (12, 'blah','blah');
SET IDENTITY_INSERT surveys OFF;
');
I hope this post helps someone else.
Upvotes: 3
Reputation: 1468
In order to execute those commands, you can do it as raw
DB::statement('SET IDENTITY_INSERT surveys ON;');
and then
DB::statement('SET IDENTITY_INSERT surveys OFF;');
DB::statement is supposed for commands that don't return data
Edit When the seeder is executed like this:
DB::statement(...);
DB::table(...)->insert(...);
DB::statement(...);
Seems that for some reason the state of IDENTITY_INSERT variable is not kept, so a way to make it work is to wrap that code in a transaction.
try {
DB::beginTransaction();
DB::statement(...);
DB::table(...)->insert(...);
DB::statement(...);
DB::commit();
} catch(Exception $e) {
DB::rollback();
}
Upvotes: 3