如何在实体框架中使用事务

本文关键字:事务 框架 实体 | 更新日期: 2023-09-27 18:03:32

假设我有两个表,AB,其中A有一个指向B的外键。

数据库可能是这样的:

A:                         |   B:
                           |
id (int) | b (foreign B)   |   id (int)
---------+--------------   |   --------
1        | 2               |   2
2        | 5               |   5
3        | 2               |

我现在希望插入一个类型为A的新对象和一个类型为B的新对象到数据库中,新的A引用新的B。此外,它们必须在同一事务中插入,因为我不能允许例如只有B被插入,但A失败。

然而,我不知道如何插入A而不首先单独插入B,因为我只在插入B后才知道B.id: B.id被数据库分配为递增的主键。

这是我想写的,但不能:

b = new B { };
a = new A { b = b };
db.Add(b);
db.Add(a);
db.SaveChanges()

我如何使用实体框架来完成这个?

谢谢!

如何在实体框架中使用事务

听起来好像你的模型没有导航属性,而只有外键属性。

在实体框架中,您可以利用导航属性来允许EF为您执行大部分关系管理的后台工作。例如:

public class A {
  public int Id {get;set;}
  public int BId {get;set;}
  public virtual B B {get;set;}
}
public class B{
  public int Id {get;set;}
  public virtual ICollection<A> AColl {get;set;}
}

下面的代码将执行单个SQL事务将aa.B插入到数据库中,当成功时,将自动填充a.Ida.BIdb.Id

//Add A and B to the database
A a = new A();
a.B = new B();
db.Add(a);
db.SaveChanges();

请注意,拥有导航属性还允许您执行其他有用的任务,例如获取所有A项以及B,如下所示:

var b = db.B.Find(someId).Include(b => b.AColl);
// we now have all the A records that have B FK
foreach (A a in b.AColl){
  //do something
}

如@CodeCaster所述,EF默认使用事务。如果需要更细粒度的控制,请将其封装在using (TransactionScope)中。见https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx。编辑:至于为什么你的代码张贴不会工作,你需要有一个导航属性从a到B,然后你只会分配说属性的值B当你实例化类型a的对象

可能是代码中的存储过程调用,过程可以是......

CREATE PROCEDURE usp_InsertRow
AS
BEGIN
  SET NOCOUNT ON;
    DECLARE @New_ID INT;
 BEGIN TRY
    BEGIN TRANSACTION;
        INSERT INTO TableB DEFAULT VALUES;
        SET @New_ID = SCOPE_IDENTITY();
        INSERT INTO TableA (B)
        VALUES (@New_ID)
    COMMIT TRANSACTION;
 END TRY
 BEGIN CATCH
   IF @@TRANCOUNT <> 0
     ROLLBACK TRANSACTION;
 END CATCH
END

正如@Ciaes在他的评论中提到的,你不需要创建一个交易来实现你所需要的。假设你有一个这样的模型:

public class A 
{
   public int Id{get;set;}
   [ForeignKey("B")]
   public int BId{get;set;}
   public virtual B B{get;set;}
}
public class B 
{
   public int Id{get;set;}
   public virtual ICollection<A> As{get;set;}
}

添加两个新实体只需要这样做:

var a = new A(){B=new B()};
db.SaveChanges();

两个实体将被插入到单个事务中。

从此msdn页面:

如果要添加的实体有对其他实体的引用如果还没有被跟踪,那么这些新的实体也会被添加到上下文,并将在下次调用时插入到数据库中SaveChanges被称为

从EF 6开始,Microsoft建议使用dbContext.Database.BeginTransaction而不是TransactionScope (https://msdn.microsoft.com/en-us/data/dn456843.aspx)

基本上你必须把事务包装在using块中,像这样:

using (var dbContextTransaction = db.Database.BeginTransaction()) 
{
    b = new B { };
    a = new A { b = b };
    db.Add(b);
    db.Add(a);
    db.SaveChanges()
    dbContextTransaction.Commit(); 
}

然后我建议你阅读工作单元模式因为它将帮助你管理事务(http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application)

我试过了,它正在工作:创建过程usp_InsertRow作为开始设置nocount on;声明@New_ID INT;开始尝试开始事务;

    INSERT INTO TableB DEFAULT VALUES;
    SET @New_ID = SCOPE_IDENTITY();
    INSERT INTO TableA (B)
    VALUES (@New_ID)
COMMIT TRANSACTION;

结束试一试开始抓@@TRANCOUNT <> 0回滚事务;