90

In Entity Framework - Is there any way to retrieve a newly created ID (identity) inside a transaction before calling 'SaveChanges'?

I need the ID for a second insert, however it is always returned as 0...

        ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;

        objectContext.Connection.Open();

        using (var transaction = objectContext.Connection.BeginTransaction())
        {
            foreach (tblTest entity in saveItems)
            {
                this.context.Entry(entity).State = System.Data.EntityState.Added;
                this.context.Set<tblTest>().Add(entity);

                int testId = entity.TestID;

                .... Add another item using testId
            }

            try
            {
                context.SaveChanges();
                transaction.Commit();
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                objectContext.Connection.Close();
                throw ex;
            }
        }

        objectContext.Connection.Close();
user1948635
  • 1,357
  • 4
  • 15
  • 22

7 Answers7

72

The ID is generated by the database after the row is inserted to the table. You can't ask the database what that value is going to be before the row is inserted.

You have two ways around this - the easiest would be to call SaveChanges. Since you are inside a transaction, you can roll back in case there's a problem after you get the ID.

The second way would be not to use the database's built in IDENTITY fields, but rather implement them yourself. This can be very useful when you have a lot of bulk insert operations, but it comes with a price - it's not trivial to implement.

EDIT: SQL Server 2012 has a built-in SEQUENCE type that can be used instead of an IDENTITY column, no need to implement it yourself.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • 3
    If I was to call 'SaveChanges' after the 1st insert, then the 2nd insert fails - would the transaction still roll the 1st insert back? – user1948635 Jul 08 '13 at 09:53
  • 10
    of course, thats what a transaction is about. every operation in a transaction will be rolled back on `rollback`. have a look at http://en.wikipedia.org/wiki/ACID – SeriousM Jul 08 '13 at 09:56
  • There is no rollback feature in Entity. Its like the chicken and egg problem. The only way is to execute a bit of pure SQL and ask the DB what the next Identity is going to be. But this is not guaranteed since somebody else can take it. The other thing is to write SQL and insert and empty row to reserve it, but you might land up with allot of empty reserved rows. – Piotr Kula Dec 02 '14 at 14:47
  • 7
    .NET has a very nice rollback feature with TransactionScope. It's enough. – zmbq Dec 02 '14 at 14:52
  • I'm little curious to know that sometimes this works. I mean before SaveChanges the ID is getting set. While Debugging I can't see the new ID, but when I see the table, it is already saved with new ID. How it is possible? check my code: https://codeshare.io/bbCuM – Firnas Jan 07 '16 at 15:28
  • @PiotrKula Use the transaction structures in .Net or at the database level of the Entity Framework context. You call the Complete() method on the transaction or it rolls back when it goes out of scope. – Suncat2000 Sep 20 '19 at 18:01
  • Ids can be retrieved prior to calling `SaveChanges()`, view my answer below. – Laurynas Lazauskas Sep 24 '21 at 07:59
  • @SeriousM NO ! Every SaveChanges is a separate transaction (e.g. to insert and get ID), if one fails nothing will rollback the first one, unless, you wrap all your code in a transaction ! – abdelgrib Oct 28 '22 at 10:40
  • Before leaves the question with this answer, check this one: https://stackoverflow.com/a/54259803/888472 – Leandro Bardelli Apr 21 '23 at 13:16
59

As others have already pointed out, you have no access to the increment value generated by the database before saveChanges() was called – however, if you are only interested in the id as a means to make a connection to another entity (e.g. in the same transaction) then you can also rely on temporary ids assigned by EF Core:

Depending on the database provider being used, values may be generated client side by EF or in the database. If the value is generated by the database, then EF may assign a temporary value when you add the entity to the context. This temporary value will then be replaced by the database generated value during SaveChanges().

Here is an example to demonstrate how this works. Say MyEntity is referenced by MyOtherEntity via property MyEntityId which needs to be assigned before saveChanges is called.

var x = new MyEntity();        // x.Id = 0
dbContext.Add(x);              // x.Id = -2147482624 <-- EF Core generated id
var y = new MyOtherEntity();   // y.Id = 0
dbContext.Add(y);              // y.Id = -2147482623 <-- EF Core generated id
y.MyEntityId = x.Id;           // y.MyEntityId = -2147482624
dbContext.SaveChangesAsync();
Debug.WriteLine(x.Id);         // 1261 <- EF Core replaced temp id with "real" id
Debug.WriteLine(y.MyEntityId); // 1261 <- reference also adjusted by EF Core

The above also works when assigning references via navigational properties, i.e. y.MyEntity = x instead of y.MyEntityId = x.Id

Felix K.
  • 14,171
  • 9
  • 58
  • 72
  • 4
    I think that this should be the accepted answer because the question was exactly about that: getting the Id for the next insert(s) that need this not-inserted-yet row/entity. Thanks for the nice discover @B12Toaster – ianis Nov 24 '20 at 15:29
  • 1
    @SérieusementPoulet I agree. This should be the accepted answer. – Metehan Mutlu Apr 08 '21 at 14:22
  • On mysql Id is still 0 after adding it to DbContext. If I look it up in the changetracker I can see a -xxxxx value, but there doesn't seem a way to access it. – UNeverNo Dec 08 '22 at 12:49
  • @UNeverNo See https://stackoverflow.com/a/66482845/221528. – glen-84 Jan 11 '23 at 19:51
8

You can retreive an ID before calling .SaveChanges() by using the Hi/Lo alhorithm. The id will be assigned to the object once it is added to dbcontext.

Example configuration with fluent api:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Entity>(e =>
    {
        e.Property(x => x.Id).UseHiLo();
    });
}

An excerpt from the relevant Microsoft article:

The Hi/Lo algorithm is useful when you need unique keys before committing changes. As a summary, the Hi-Lo algorithm assigns unique identifiers to table rows while not depending on storing the row in the database immediately. This lets you start using the identifiers right away, as happens with regular sequential database IDs.

Laurynas Lazauskas
  • 2,855
  • 2
  • 21
  • 26
7

If your tblTest entity is connected to other entities that you want to attach, you don't need to have the Id to create the relation. Lets say tblTest is attached to anotherTest object, it the way that in anotherTest object you have tblTest object and tblTestId properties, in that case you can have this code:

using (var transaction = objectContext.Connection.BeginTransaction())
    {
        foreach (tblTest entity in saveItems)
        {
            this.context.Entry(entity).State = System.Data.EntityState.Added;
            this.context.Set<tblTest>().Add(entity);

            anotherTest.tblTest = entity;
            ....
        }
    }

After submitting the relation would be created and you don't need to be worry about Ids and etc.

Armen
  • 1,083
  • 2
  • 10
  • 18
6

@zmbq is right, you can only get the id after calling save changes.

My suggestion is that you should NOT rely on the generated ID's of the database. The database should only a detail of your application, not an integral and unchangeable part.

If you can't get around that issue use a GUID as an identifier due it's uniqueness. MSSQL supports GUID as a native column type and it's fast (though not faster than INT.).

Cheers

SeriousM
  • 3,374
  • 28
  • 33
  • Unfortunately changing the ID fields now would cause problems with other aspects of the application, would mean a major change that I am trying to avoid. – user1948635 Jul 08 '13 at 09:57
  • but it is an INT column, right? maybe you can insert a TICK or a unique number you create your own. – SeriousM Jul 08 '13 at 10:00
  • It is, but there are a large number of SSIS packages that run against the database expecting an identity column... – user1948635 Jul 08 '13 at 10:27
  • If I try to add several entities that are related to each other I will get constraint errors, since EF does not seem to update the saved models in the order I added them or worked with them. So the entity that needs the ID of the previous entity might tried to be saved before the entity on which the constraint is placed. Even if it does it in the correct order, dbcontext does not update the ID and it just stays 0 within the saveChanges method, really poop, one way or the other. – Piotr Kula Dec 02 '14 at 14:48
0

A simple work around for this would be

var ParentRecord = new ParentTable () {
SomeProperty = "Some Value",
AnotherProperty = "Another Property Value"
};

ParentRecord.ChildTable.Add(new ChildTable () {
ChildTableProperty = "Some Value",
ChildTableAnotherProperty = "Some Another Value"
});

db.ParentTable.Add(ParentRecord);

db.SaveChanges();

Where ParentTable and ChildTable are two tables connected with Foregin key.

Hamza Khanzada
  • 1,439
  • 1
  • 22
  • 39
0

You can look up the value in the ChangeTracker like this:

var newEntity = new MyEntity();
var newEntity.Property = 123;
context.Add(newEntity);

//specify a logic to identity the right entity here:
var entity = context.ChangeTracker.Entries()
    .FirstOrDefault(e => e.Entity is MyEntity myEntity && 
        myEntity.Property == newEntity.Property);
//In this case we look up the value for an autogenerated id of type int/long
 //it will be a negative value like -21445363467
var value = entity.Properties?
    .FirstOrDefault(pe => pe.Metadata.GetColumnName() == nameof(MyEntity.Id))?.CurrentValue;    
//if you set it on another entity, it will be replaced on SaveChanges()

My setup was mysql 5.7, but should work in other environments also.

UNeverNo
  • 549
  • 3
  • 8
  • 29