Skip to content

Instantly share code, notes, and snippets.

@Patabugen
Created June 28, 2022 18:26
Show Gist options
  • Save Patabugen/721042c0b8b35b217f1dcacca95fc3a9 to your computer and use it in GitHub Desktop.
Save Patabugen/721042c0b8b35b217f1dcacca95fc3a9 to your computer and use it in GitHub Desktop.
Eloquent Model event hook to allow inserting primary keys into SQL Server tables with IDENTITY_INSERT = off
<?php
class Transaction extends Model
{
protected $primaryKey = 'TransactionID';
/**
* Using Laravel to export data from a SQL Server Express 15.0 I ran into an issue when creating test models
* using my Eloquent Model Factory on some tables which did not have an Auto Incrementing primary
* key, and where the table was set to disallow inserting the Primary Key (IDENTITY_INSERT is set to OFF).
*
* The table settings cannot be changed permanently (as per SQL's docs) so we needed to run a query
* `SET IDENTITY_INSERT [Transaction] ON;` right before the insert.
*
* The hooked event below will both work out what the next ID should be if one is not provided,
* and then set IDENTITY_INSERT value just in time.
*
* This is the error this solves:
* SQLSTATE[23000]: [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Cannot insert explicit value for identity column in table 'Transactions' when IDENTITY_INSERT is set to OFF.
*
*
* # Known Problems
* I'm only using this in tests for a short term data-transfer project, so it doesn't need to be perfect. However
* if you're going to use this it's worth bearing in mind a few known problems:
* - Getting the next ID by querying for max() is always a bad idea.
* - I did look at wrapping it in a transaction, but that would require an `afterExecuting` callback too.
* - It could be worth querying for the next ID inside the callback, but that would need a bit more thinking
* so we can be sure to replace the correct binding.
*/
public static function booted()
{
static::creating(function(self $model) {
$pkColumn = $model->primaryKey;
$tableName = $model->table;
if (empty($model->$pkColumn)) {
$lastPk = $model->getQuery()->max($pkColumn);
$model->$pkColumn = $lastPk + 1;
}
$model->getConnection()->beforeExecuting(function($query, $bindings, \Illuminate\Database\Connection $connection) use ($tableName) {
// Perform some rudimentary escaping. SQL Server allows any character in the table name
// so we will remove characters SQL Server uses to wrap the table name and wrap
// it ourselves.
$tableName = str_replace(['[', ']', '`'], '', $tableName);
$connection->getPdo()->exec('SET IDENTITY_INSERT ['.$tableName.'] ON;');
$connection->commit();
});
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment