80

在实体框架中 - 有没有办法在调用“SaveChanges”之前在事务中检索新创建的 ID(身份)?

我需要第二次插入的 ID,但它总是返回为 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();
4

6 回答 6

66

ID 由数据库在将行插入表后生成。在插入行之前,您不能询问数据库该值将是什么。

您有两种解决方法 - 最简单的方法是调用SaveChanges. 由于您在事务中,因此您可以回滚以防在获取 ID 后出现问题。

第二种方法是不使用数据库的内置IDENTITY字段,而是自己实现它们。当您有很多批量插入操作时,这可能非常有用,但它是有代价的——实现起来并非易事。

编辑:SQL Server 2012 有一个内置的 SEQUENCE 类型,可以用来代替 IDENTITY 列,无需自己实现。

于 2013-07-08T09:50:21.327 回答
48

正如其他人已经指出的那样,您无法访问在saveChanges()调用之前由数据库生成的增量值 - 但是,如果您只对id作为与另一个实体建立连接(例如在同一事务中)的方式感兴趣,那么您还可以依赖EF Core 分配的临时 ID

根据所使用的数据库提供程序,值可能由 EF 或在数据库中生成客户端。如果该值是由数据库生成的,那么EF 可能会在您将实体添加到上下文时分配一个临时值。然后,此临时值将被 SaveChanges() 期间数据库生成的值替换。

这是一个示例来演示它是如何工作的。Say由需要在调用之前分配的via 属性MyEntity引用。MyOtherEntityMyEntityIdsaveChanges

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

当通过导航属性分配引用时,上述方法也有效,即y.MyEntity = x代替y.MyEntityId = x.Id

于 2019-01-18T18:45:42.520 回答
7

如果您的 tblTest 实体连接到您要附加的其他实体,则您不需要 Id 来创建关系。假设 tblTest 附加到 anotherTest 对象,它的方式在 anotherTest 对象中你有 tblTest 对象和 tblTestId 属性,在这种情况下你可以有这个代码:

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;
            ....
        }
    }

提交后将创建关系,您无需担心 Id 等。

于 2015-07-12T21:51:16.860 回答
6

@zmbq 是对的,你只能在调用 save changes 后获取 id。

我的建议是您不应该依赖生成的数据库 ID。数据库应该只是你应用程序的一个细节,而不是一个不可改变的部分。

如果您无法解决该问题,请使用 GUID 作为标识符,因为它具有唯一性。MSSQL 支持 GUID 作为本机列类型,而且速度很快(虽然不比 INT 快)。

干杯

于 2013-07-08T09:55:13.490 回答
3

可以.SaveChanges()使用 Hi/Lo 算法在呼叫之前检索 ID 。一旦添加到 dbcontext 中,该 id 将被分配给对象。

使用 fluent api 的示例配置:

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

相关微软文章的摘录:

当您在提交更改之前需要唯一键时,Hi/Lo 算法很有用。总而言之,Hi-Lo 算法为表行分配唯一标识符,而不依赖于立即将行存储在数据库中。这使您可以立即开始使用标识符,就像使用常规的顺序数据库 ID 一样。

于 2021-09-13T07:49:37.653 回答
0

一个简单的解决方法是

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();

whereParentTableChildTableare 两个表用 Foregin 键连接。

于 2019-02-08T09:05:47.583 回答