【问题标题】:Entity Framework - retrieve ID before 'SaveChanges' inside a transaction实体框架 - 在事务中的“SaveChanges”之前检索 ID
【发布时间】:2013-07-05 14:30:05
【问题描述】:

在实体框架中 - 有没有办法在调用“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();

【问题讨论】:

标签: c# asp.net entity-framework


【解决方案1】:

可以在调用 .SaveChanges() 之前使用 Hi/Lo 算法检索 ID。将 id 添加到 dbcontext 后将分配给对象。

使用 fluent api 的示例配置:

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

摘自相关Microsoft article

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

【讨论】:

    【解决方案2】:

    一个简单的解决方法是

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

    其中ParentTableChildTable 是用Foregin 键连接的两个表。

    【讨论】:

      【解决方案3】:

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

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

      这里有一个例子来演示它是如何工作的。假设MyEntityMyOtherEntity 通过属性MyEntityId 引用,该属性需要在调用saveChanges 之前分配。

      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
      

      上述方法也适用于assigning references via navigational properties,即y.MyEntity = x 而不是y.MyEntityId = x.Id

      【讨论】:

      • 我认为这应该是公认的答案,因为问题就是这样:获取下一个需要这个尚未插入的行/实体的插入的 Id。感谢@B12Toaster 的精彩发现
      • @SérieusementPoulet 我同意。这应该是公认的答案。
      【解决方案4】:

      如果您的 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等。

      【讨论】:

        【解决方案5】:

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

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

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

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

        【讨论】:

        • 如果我在第一次插入后调用“SaveChanges”,那么第二次插入失败 - 事务是否仍会将第一次插入回滚?
        • 当然,这就是交易的意义所在。事务中的每个操作都将在rollback 上回滚。看看en.wikipedia.org/wiki/ACID
        • Entity 没有回滚功能。就像鸡和蛋的问题。唯一的方法是执行一些纯 SQL 并询问数据库下一个身份将是什么。但这不能保证,因为其他人可以接受。另一件事是编写 SQL 并插入和空行来保留它,但你可能会得到分配空的保留行。
        • .NET 具有非常好的 TransactionScope 回滚功能。够了。
        • 我很想知道有时这行得通。我的意思是在 SaveChanges 之前设置 ID。在调试时我看不到新 ID,但是当我看到表格时,它已经用新 ID 保存了。怎么可能?检查我的代码:codeshare.io/bbCuM
        【解决方案6】:

        @zmbq 是对的,只有调用save changes才能获取id。

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

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

        干杯

        【讨论】:

        • 不幸的是,现在更改 ID 字段会导致应用程序的其他方面出现问题,这意味着我试图避免进行重大更改。
        • 但它是一个 INT 列,对吗?也许您可以插入一个 TICK 或您自己创建的唯一编号。
        • 是的,但是有大量的 SSIS 包针对需要标识列的数据库运行...
        • 如果我尝试添加多个相互关联的实体,我会收到约束错误,因为 EF 似乎没有按照我添加它们或使用它们的顺序更新保存的模型。因此,需要前一个实体 ID 的实体可能会尝试在放置约束的实体之前保存。即使它以正确的顺序执行,dbcontext 也不会更新 ID,它只是在 saveChanges 方法中保持为 0,真的大便,一种或另一种方式。
        猜你喜欢
        • 1970-01-01
        • 2023-04-07
        • 2012-09-06
        • 1970-01-01
        • 2012-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多