How to turn on identity-insert in .net core
Solution 1
In EF Core 1.1.2, I got this to work with transactions. In my "database initializer" that put seed data into the tables. I used the technique from this EF6 answer. Here's a sample of the code:
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
Solution 2
Had to deal with the same issue and this seems to be a clean solution.
Credit to >> https://github.com/dotnet/efcore/issues/11586
I have made some changes so it now works with .Net Core 3.1 + (Tested in .Net 5) and also added this Method SaveChangesWithIdentityInsert
public static class IdentityHelpers
{
public static Task EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: true);
public static Task DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: false);
private static Task SetIdentityInsert<T>(DbContext context, bool enable)
{
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
return context.Database.ExecuteSqlRawAsync(
$"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static void SaveChangesWithIdentityInsert<T>(this DbContext context)
{
using var transaction = context.Database.BeginTransaction();
context.EnableIdentityInsert<T>();
context.SaveChanges();
context.DisableIdentityInsert<T>();
transaction.Commit();
}
}
Usage
var data = new MyType{SomeProp= DateTime.Now, Id = 1};
context.MyType.Add(data);
context.SaveChangesWithIdentityInsert<MyType>();
Solution 3
Improved solution based on NinjaCross' answer.
This code is added directly in the database context class and allows to save changes by also specifying that identity insert is needed for a certain type (mapped to a table).
Currently, I have only used this for integrative testing.
public async Task<int> SaveChangesWithIdentityInsertAsync<TEnt>(CancellationToken token = default)
{
await using var transaction = await Database.BeginTransactionAsync(token);
await SetIdentityInsertAsync<TEnt>(true, token);
int ret = await SaveChangesExAsync(token);
await SetIdentityInsertAsync<TEnt>(false, token);
await transaction.CommitAsync(token);
return ret;
}
private async Task SetIdentityInsertAsync<TEnt>(bool enable, CancellationToken token)
{
var entityType = Model.FindEntityType(typeof(TEnt));
var value = enable ? "ON" : "OFF";
string query = $"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}";
await Database.ExecuteSqlRawAsync(query, token);
}
Steve Nyholm
's answer works fine, but I will provide some extra explanation and some generic code with exception handling.
Normally the context takes care of the transaction, but in this case manually taking care of it is required. Why?
Database context will generate a BEGIN TRAN
after the SET IDENTITY_INSERT
is issued. This will make transaction's inserts to fail since IDENTITY_INSERT seems to affect tables at session/transaction level.
So, everything must be wrapped in a single transaction to work properly.
Here is some useful code to seed at key level (as opposed to table level):
Extensions.cs
[Pure]
public static bool Exists<T>(this DbSet<T> dbSet, params object[] keyValues) where T : class
{
return dbSet.Find(keyValues) != null;
}
public static void AddIfNotExists<T>(this DbSet<T> dbSet, T entity, params object[] keyValues) where T: class
{
if (!dbSet.Exists(keyValues))
dbSet.Add(entity);
}
DbInitializer.cs
(assumes that model class name is the same as table name)
private static void ExecuteWithIdentityInsertRemoval<TModel>(AspCoreTestContext context, Action<AspCoreTestContext> act) where TModel: class
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TModel).Name + " ON;");
context.SaveChanges();
act(context);
context.SaveChanges();
transaction.Commit();
}
catch(Exception)
{
transaction.Rollback();
throw;
}
finally
{
context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TModel).Name + " OFF;");
context.SaveChanges();
}
}
}
public static void Seed(AspCoreTestContext context)
{
ExecuteWithIdentityInsertRemoval<TestModel>(context, ctx =>
{
ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 1, ModelCode = "Test model #1" }, 1);
ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 2, ModelCode = "Test model #2" }, 2);
});
}
Solution 4
@Steve Nyholm answer is OK, But in .Net core 3 ExecuteSqlCommand is Obsolete, ExecuteSqlInterpolated replacement of ExecuteSqlCommand:
using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
var user = new User {Id = 123, Name = "Joe"};
db.Users.Add(user);
db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users ON;");
db.SaveChanges();
db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
}
Solution 5
The solution proposed by @sanm2009 contains some nice ideas.
However the implementation has some imperfections related to the misusage of Task/async/await.
The method SaveChangesWithIdentityInsert does not return Task, nor await for the calls to EnableIdentityInsert and DisableIdentityInsert.
This could lead to undesired side effects.
The following implementations supports both async/await, and non-awaitable paradigms.
#region IDENTITY_INSERT
public static void EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, true);
public static void DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, false);
private static void SetIdentityInsert<T>([NotNull] DbContext context, bool enable)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
context.Database.ExecuteSqlRaw($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static void SaveChangesWithIdentityInsert<T>([NotNull] this DbContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
using var transaction = context.Database.BeginTransaction();
context.EnableIdentityInsert<T>();
context.SaveChanges();
context.DisableIdentityInsert<T>();
transaction.Commit();
}
#endregion
#region IDENTITY_INSERT ASYNC
public static async Task EnableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, true);
public static async Task DisableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, false);
private static async Task SetIdentityInsertAsync<T>([NotNull] DbContext context, bool enable)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var entityType = context.Model.FindEntityType(typeof(T));
var value = enable ? "ON" : "OFF";
await context.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
}
public static async Task SaveChangesWithIdentityInsertAsync<T>([NotNull] this DbContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
await using var transaction = await context.Database.BeginTransactionAsync();
await context.EnableIdentityInsertAsync<T>();
await context.SaveChangesAsync();
await context.DisableIdentityInsertAsync<T>();
await transaction.CommitAsync();
}
#endregion
Related videos on Youtube
Alexei - check Codidact
C#.NET & SQL Server developer with a strong Angular flavor. If you are interested in a software development community with less drama and more tolerance to "opinioned" questions, check Software Development Codidact.
Updated on April 08, 2022Comments
-
Alexei - check Codidact about 2 years
I made a few tables in EF and entered in some seed data where I give value to a few columns with a primary key. When I run the application I am getting the error message:
Cannot insert explicit value for identity column in table 'Persons' when IDENTITY_INSERT is set to OFF.
How do I turn it on? I read on here to use:
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
above the property that is a primary key. I am still getting the same error message unfortunately. Please help.
I added
[DatabaseGenerated(DatabaseGeneratedOption.None)]
to all my properties that have a primary key. When I ran the migration I can see that the identity column is removed, But I am still getting the same error message.When I go into SQL SEO I can still see the identity column on my primary key. I tried refreshing the database. What am I doing wrong? The only thing I can do is go into properties and remove the identity, but why can't I do it the way mentioned above?
-
Gert Arnold over 7 years
-
Bill Sambrone over 7 yearsDo you want the primary key column to never be an identity column, or just temporarily turn off it's identity-ness so you can manually assign some key values for a few records?
-
kimbaudi over 7 yearsThe SQL statement to turn IDENTITY_INSERT off is
SET IDENTITY_INSERT [dbo].[mytable] OFF
assuming your table name ismytable
and your schema isdbo
. The reason for IDENTITY_INSERT being set to off is to prevent you from manually setting the value for the primary key. -
Admin over 7 yearsThe error message indicates that I need to turn on identity_insert. I am trying to temporary add seed data where values are entered into a primary key. Do I use the sql statement (not sure where to even type it in) or do I use the data annotation above?
-
-
DharmaTurtle about 5 yearsThis code uses
context.Model.FindEntityType(typeof(T)).Relational().TableName
to bypass the assumption that the model class name is the same as the table name: github.com/aspnet/EntityFrameworkCore/issues/11586 -
Alexei - check Codidact about 5 years@DharmaTurtle - that's better. I will give a try and update the answer. Thanks.
-
Captain Prinny over 4 yearsDo you need to call save changes after executing a sql command?
-
Qudus almost 4 yearsJust to point out if you get this error in the
ExecuteSqlInterpolated
method:cannot convert from string to System.FormattableString
in VS, it won't stop it from working, but simply use it this way if you want it gone with a dollar string interpolation symbol –db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users ON");
-
Sras almost 4 yearsis there way to do on/off once for 20 tables
-
NinjaCross over 3 yearsThis solution contains some nice ideas. However the implementation has some imperfections related to the misusage of Task/async/await. The method SaveChangesWithIdentityInsert does not return Task, nor await for the calls to EnableIdentityInsert and DisableIdentityInsert. This could lead to undesired side effects. The following implementations supports both async/await, and non-awaitable paradigms. stackoverflow.com/a/65732831/203370
-
Selçuk Öztürk about 3 yearsYou should replace the second
await context.EnableIdentityInsertAsync<T>();
with DisableIdentityInsertAsync. :) -
NinjaCross about 3 yearsThanks @SelçukÖztürk
-
ChickenOverlord almost 3 years@Sras SQL Server only allows IDENTITY_INSERT to be enabled for one table at a time. If you try to set it for another table before turning it off for the previous table SQL server will throw an error: docs.microsoft.com/en-us/sql/t-sql/statements/…
-
Mahdi Abyaznezhad over 2 yearsThanks, Mohammad. It works for me with the Async method too (.Net core5). SaveChangesAsync()