15

This is the query:

using (var db = new AppDbContext())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
}

When executed, the Id of the inserted record, on a new table, is still 1.

NEW: When I use either the transaction, or TGlatzer's answer, I get the exception:

Explicit value must be specified for identity column in table 'Items' either when IDENTITY_INSERT is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity column.

ProfK
  • 49,207
  • 121
  • 399
  • 775
  • 2
    Based on this answer [Entity Framework IDENTITY_INSERT ON doesn't work](http://stackoverflow.com/a/16215233/6741868), you turned Identity Insert on just briefly, and it didn't include the insert. If its possible, try creating a big sql query string starting with `"SET IDENTITY_INSERT Test.Items ON;"` followed by your `INSERT INTO...` and finally `"SET IDENTITY_INSERT Test.Items OFF;"` (All in one query). Or, you can take a look at [TransactionScope Class](https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope(v=vs.110).aspx). – Keyur PATEL Feb 02 '17 at 06:33
  • @KeyurPATEL Using a direct `INSERT` query really defeats the purpose of an ORM. I much prefer the transaction option, but also prefer the built-in transaction, like in Aananda's answer. – ProfK Feb 02 '17 at 10:20
  • @ProfK : For the exception,can you please go to the model designer(edmx) and select the property Id (This is the Identity column) and modify StoreGenratedPattern to None instead of Identity ? You must change this,since you are inserting the identity column.This should fix the exception. – Rangesh Feb 04 '17 at 08:16
  • @Rangesh IDENTITY_INSERT is for exceptional tasks. If I change the pattern to none, then I won't get identity values on normal inserts. That is a terrible solution. – ProfK Feb 04 '17 at 09:15
  • 1
    May be if nothing works, one option is to map stored procedure for insert for this entity. http://www.entityframeworktutorial.net/entityframework6/code-first-insert-update-delete-stored-procedure-mapping.aspx . And you can pass a flag which would set identify insert depending on the flag value. – Developer Feb 04 '17 at 09:33
  • See http://stackoverflow.com/questions/13086006/how-can-i-force-entity-framework-to-insert-identity-columns – Serg Feb 04 '17 at 09:37
  • The answer was in the first link in the first comment. Maybe a bit concealed, but the way to go is to open and close the db connection yourself. Could have saved you some rep points :) – Gert Arnold Feb 04 '17 at 10:29
  • 4
    @GertArnold The main issue though is not how to maintain the connection, but how to let EF include the Id value in the insert command. – Ivan Stoev Feb 04 '17 at 11:29

8 Answers8

14

According to this previous Question you need to begin a transaction of your context. After saving the change you have to restate the Identity Insert column too and finally you must have to commit the transaction.

using (var db = new AppDbContext())
using (var transaction = db .Database.BeginTransaction())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
    transaction.Commit();
}
Community
  • 1
  • 1
Ananda G
  • 2,389
  • 23
  • 39
11

I did not honor the tags of the question telling this is about EF6.
This answer will work for EF Core

The real culprit here is not the missing transaction, but the small inconvenience, that Database.ExectueSqlCommand() will not keep the connection open, when not explicitly opened before.

using (var db = new AppDbContext())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.OpenConnection();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
}

will also do, since SET IDENTITY_INSERT [...] ON/OFF will be bound to your connection.

TGlatzer
  • 5,815
  • 2
  • 25
  • 46
  • I get an exception trying this, and with the other answer as well. Please see my edit to the OP for details. – ProfK Feb 04 '17 at 07:54
  • Oops. I somehow assumed EF Core.. My Code works in EF Core :) - Or worked until your edit – TGlatzer Feb 06 '17 at 11:00
  • Haha, a rock and a hard place. The code, as changed by me, compiles and runs for me on EF 6, but still doesn't do what is intended. – ProfK Feb 06 '17 at 15:14
  • 1
    For EF Core 2 this code actually works. Using OpenConnection before ExecuteSqlCommand keeps the connection open and the identity insert is possible. – Ivaylo Apr 23 '19 at 15:07
  • after spending 4 hours this finally worked – M.Hamza Ali Jun 17 '22 at 10:47
4

To force EF writing ID of your entity, you have to configure the ID as not store generated otherwise EF will never include the ID in the insert statement.

So, you need to change the model on the fly and configure the entity ID as you need.
The problem is that the model is cached and is quite tricky to change it on the fly (I'm quite sure I've done it but actually I can't find the code, probably I throwed it away). The shortest way is to create two different contexts where you configure your entity in two different ways, as DatabaseGeneratedOption.None (when you need to write the ID) and as DatabaseGeneratedOption.Identity (when you need the autonumbering ID).

bubi
  • 6,414
  • 3
  • 28
  • 45
  • But even then, I will have to do a migration each time I use a different one of the two contexts. If the column is an `Identity` column, and I use the context with `DatabaseGeneratedOption.None`, I will get an exception from the db when I try and insert a value into that column. I think it better to use a stored proc for the very rare event I need identity insert. – ProfK Feb 06 '17 at 15:19
  • 1
    After several tries, I stopped to migrate data content using EF as well. I do data migration using ADO.Net. Stored procedures is the fastest (best) way if you don't need to target different DBMSs – bubi Feb 07 '17 at 10:29
  • The code you forgot could be `modelBuilder.Conventions.Add();` in overriding the OnModelCreating method of DbContext. – Bamdad Mar 14 '21 at 18:13
4

I had a similar issue. In my production code the entities are relying on identity generation. But for integration testing I need to manually set some IDs. Where I don't need to set them explicitly I generated them in my test data builders. To achieve this I created a DbContext inheriting the one in my production code and configured the identity generation for each entity like this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<Entity1>().Property(e => e.Id).ValueGeneratedNever();
    modelBuilder.Entity<Entity2>().Property(e => e.Id).ValueGeneratedNever();
    ...
}

But that wasn't enough and I had to disable the SQL Server IDENTITY_INSERT. This worked when inserting data in a single table. But when you have entities related to one another and you want to insert a graph of objects this fails on DbContext.SaveChanges(). The reason is that as per SQL Server documentation you can have IDENTITY_INSERT ON just for one table at a time during a session. My colleague suggested to use a DbCommandInterceptor which is similar to the other answer to this question. I made it work for INSERT INTO only but the concept could be expanded further. Currently it intercepts and modifies multiple INSERT INTO statements within a single DbCommand.CommandText. The code could be optimized to use Span.Slice in order to avoid too much memory due to string manipulation but since I couldn't find a Split method I didn't invest time into this. I am using this DbCommandInterceptor for integration testing anyway. Feel free to use it if you find it helpful.

/// <summary>
/// When enabled intercepts each INSERT INTO statement and detects which table is being inserted into, if any.
/// Then adds the "SET IDENTITY_INSERT table ON;" (and same for OFF) statement before (and after) the actual insertion.
/// </summary>
public class IdentityInsertInterceptor : DbCommandInterceptor
{
    public bool IsEnabled { get; set; }

    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        if (IsEnabled)
        {
            ModifyAllStatements(command);
        }

        return base.ReaderExecuting(command, eventData, result);
    }

    private static void ModifyAllStatements(DbCommand command)
    {
        string[] statements = command.CommandText.Split(';', StringSplitOptions.RemoveEmptyEntries);
        var commandTextBuilder = new StringBuilder(capacity: command.CommandText.Length * 2);

        foreach (string statement in statements)
        {
            string modified = ModifyStatement(statement);
            commandTextBuilder.Append(modified);
        }

        command.CommandText = commandTextBuilder.ToString();
    }

    private static string ModifyStatement(string statement)
    {
        const string insertIntoText = "INSERT INTO [";
        int insertIntoIndex = statement.IndexOf(insertIntoText, StringComparison.InvariantCultureIgnoreCase);
        if (insertIntoIndex < 0)
            return $"{statement};";

        int closingBracketIndex = statement.IndexOf("]", startIndex: insertIntoIndex, StringComparison.InvariantCultureIgnoreCase);
        string tableName = statement.Substring(
            startIndex: insertIntoIndex + insertIntoText.Length,
            length: closingBracketIndex - insertIntoIndex - insertIntoText.Length);

        // we should probably check whether the table is expected - list with allowed/disallowed tables
        string modified = $"SET IDENTITY_INSERT [{tableName}] ON; {statement}; SET IDENTITY_INSERT [{tableName}] OFF;";
        return modified;
    }
}

Ceco
  • 1,586
  • 3
  • 16
  • 23
  • Note that EF sometimes relies on the number of inserted items (i.e. uses `@@ROWCOUNT`). These queries will not work because `@@ROWCOUNT` is set to 0 after calling `SET IDENTITY_INSERT [{tableName}] OFF;` – Marcus Krahl Aug 12 '20 at 07:31
  • Great solution but be careful: if you're using `SaveChangesAsync`, this will NOT work, You have to also override `ReaderExecutingAsync`, whose code is almost the same. – SolarBear May 26 '23 at 18:11
3

The answer works for Entity Framework 6 Just use IDENTITY_INSERT outside transaction

using (var db = new AppDbContext())
{
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    using (var transaction = db .Database.BeginTransaction())
    {
       var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
       db.IdentityItems.Add(item);
       db.SaveChanges();
       transaction.Commit();
    }
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
}
pauluss
  • 31
  • 2
2

This must never be used in production code,it is just for fun
I see that mine is still the accepted answer,again, not do use this (to solve this problem), check the other answers below

I do not suggest this because it is a crazy hack but anyway.

I think we can achieve it by intercepting the SQL command and changing the command text
(you can inherit from DbCommandInterceptor and override ReaderExecuting)

I don't have a working example at the moment and I have to go but I think it is doable

Sample code

    public class MyDbInterceptor : DbCommandInterceptor
    {
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {

            if (is your table)
            {
                command.CommandText = "Set Identity off ,update insert into ,Set Identity off"
                return;
            }
            base.ReaderExecuting(command, interceptionContext);

        }

    }

ORMs are a nice abstraction and I really like them but I don't think it makes sense to try to "hack" them to support lower(closer to the db) level operations.
I try to avoid stored procs but I think in this (as you said exceptional) case I think you should use one

George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • On second thought,I think I'll delete this.I don't think you can get the Id you need from here... – George Vovos Feb 04 '17 at 12:41
  • Well,you can add a property on your context and then access it from interceptionContext.DbContexts.I won't delete the answer but I hope there is a better solution... – George Vovos Feb 04 '17 at 13:05
  • The techie in me loves the interception idea, but a stored proc is probably the neatest answer. – ProfK Feb 04 '17 at 17:02
  • 1
    Oh, and I love the idea of the interceptor for inspecting the SQL queries, vs. the overkill of the SQL Profiler. This way I can even trace log the queries. – ProfK Feb 04 '17 at 17:08
  • But ain't this execute for all the queries for that entity? Or am I missing something? Somewhere in the comments, I saw that you need this behavior-setting identify off - only for exceptional tasks. And yeah, command intercepters are really useful for logging queries; I do logging via intercepters based on some flag in the config so that I could control this. – Developer Feb 05 '17 at 13:29
  • @Developer I suspect Profk chose the sp approch. Yes,it executes for all queries .The IsYourTable method would have to check that the command is 1 Insert Into statement on the specific table. (the "interceptor" part of the answer is(might be) a solution to the problem but I also mentioned that it is not a good one,sp or something else that is working are better approaches) – George Vovos Feb 05 '17 at 13:34
  • Agreed. I had something in my mind - mapping stored proc for insert operation for that entity. EF supports this out of the box via fluent api - http://www.entityframeworktutorial.net/entityframework6/code-first-insert-update-delete-stored-procedure-mapping.aspx .May be he could have a `NotMapped` property `bool IsIdentityInsert` in the entity and perform the identity on or off logic in stored proc. Again not sure whether this is an elegant way of achieving what OP needed. – Developer Feb 05 '17 at 13:49
  • I don't see any convincing evidence here that the ID value (that EF doesn't provide, see above comments) can be hacked into the insert command. – Gert Arnold Feb 05 '17 at 21:44
  • @GertArnold You can add a property on the context (the user has to set it of course ) and then you can cast interceptionContext.DbContexts.First() to your context and access it. It doesn't make sense but you can pass some info to ReaderExecuting... – George Vovos Feb 05 '17 at 21:53
  • Well, a better way would probably be to get the ID value (or values, because in other scenarios multiple entities may be inserted) from the entity entries, but the hardest part is to modify the SQL adequately. Even if it works, it's unacceptably dirty imo. – Gert Arnold Feb 05 '17 at 21:58
  • @GertArnold I agree it's unacceptably dirty and I don't think anyone can update the sql safely and correctly that's why I suggested the stored proc solution. I liked Akash Kava's answer but I guessed it didn't work since the OP didn't mark it as the solution. – George Vovos Feb 05 '17 at 22:05
  • So did I. Well, I'm glad we agree, and you already demonstrated some healthy self-evaluation. After having been around a couple of years I'm beyond surprise about OP's decisions what to accept :) – Gert Arnold Feb 05 '17 at 22:09
  • @GertArnold I would never use something like that and I'm pretty sure ProfK (or any other sane person) is not going to use it . It is an interesting abuse of a feature though (and someone can think of a valid use case for it ,after all it is allowed) – George Vovos Feb 05 '17 at 22:20
  • I used this approach for my integration tests. My answer is below - https://stackoverflow.com/a/60450172/608971. – Ceco Feb 28 '20 at 10:51
1

Even if you turn off IDENTITY_INSERT, you have just told SQL that I will send you Identity, you did not tell entity framework to send Identity to SQL server.

So basically, you have to create DbContext as shown below ..

// your existing context
public abstract class BaseAppDbContext : DbContext { 


    private readonly bool turnOfIdentity = false;
    protected AppDbContext(bool turnOfIdentity = false){
        this.turnOfIdentity = turnOfIdentity;
    }


    public DbSet<IdentityItem> IdentityItems {get;set;}

    protected override void OnModelCreating(DbModelBuilder modelBuilder){
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<IdentityItem>()
           .HasKey( i=> i.Id )

           // BK added the "Property" line.
           .Property(e => e.Id)
           .HasDatabaseGeneratedOption(
               turnOfIdentity ?
                   DatabaseGeneratedOption.None,
                   DatabaseGeneratedOption.Identity
           );

    }
}

public class IdentityItem{

}


public class AppDbContext: BaseAppDbContext{
    public AppDbContext(): base(false){}
}

public class AppDbContextWithIdentity : BaseAppDbContext{
    public AppDbContext(): base(true){}
}

Now use it this way...

using (var db = new AppDbContextWithIdentity())
{
    using(var tx = db.Database.BeginTransaction()){
       var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
       db.IdentityItems.Add(item);
       db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
       db.SaveChanges();
       db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
       tx.Commit();
    }
}
Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • I like your thinking here, but it does seem you haven't tried your code. Reconfiguring `IdentityItem` causes the EF internal model to change, so the query throws the following exception: `System.InvalidOperationException: The model backing the 'AppDbContext' context has changed since the database was created.`. And you can't chain `HasDatabaseGeneratedOption` off `HasKey`, I had to do it like my edit in your code. – ProfK Feb 04 '17 at 10:10
  • 1
    @ProfK how about now? Creating two different context, this will cause model to be created once per context type. Sorry I forgot about the cache. If this does not work then only alternative is to create two separate contexts. – Akash Kava Feb 04 '17 at 11:43
  • 1
    I think this is a working solution. It's not a bad idea anyway to have specialized contexts for special tasks or for several segregations of an application. – Gert Arnold Feb 04 '17 at 15:49
0

I had a very similar problem.

The solution was something like:

db.Database.ExecuteSqlCommand("disable trigger all on  myTable ;") 
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable  ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable  OFF");
db.Database.ExecuteSqlCommand("enable trigger all on  myTable ;") 

In my case, the message Explicit value must be specified for identity... was because on insert a trigger called and would insert something else.

ALTER TABLE myTable NOCHECK CONSTRAINT all

Can also be usefull

Madlozoz
  • 357
  • 4
  • 15