EntityFramework 事务处理

默认情况下,当EF调用SaveChanges()时,会把生成的所有SQL命令“包”到一个“事务(transaction)”中,只要有一个数据更新操作失败,整个事务将回滚。

在多数情况下,如果你总在数据更新操作代码中使用一个而不是多个DbContext对象,并且只是在最后调用一次SaveChanges(),那么EF的默认事务处理机制己经够用了,无需做额外的事情。

然而,如果出现以下的情形,你就必须显式地处理事务了。

第一种情况:你需要分阶段地保存数据,因而需要多次调用SaveChanges()或者执行修改数据库的SQL命令。

请看以下示例代码:

 

复制代码
using (var context = new MyDbContext())

{
    try
     {
        Person3 p = context.People3.First();

        p.Name ="newName" + (new Random().Next(1, 100));

        context.SaveChanges();

        context.Database.ExecuteSqlCommand("update Person3 setDescription={0} where Person3Id={1}",

                            "DescriptionModified at " + DateTime.Now.ToShortTimeString(),

                            p.Person3Id);
        p.age *= 2;
        context.SaveChanges();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
     }
}
复制代码

 

上述代码中,调用两次SaveChanges(),还有一次执行Update命令。

如果在最后一次SaveChanges()中出现异常,虽然最后一次没成功,但你会发现前两次数据己经保存!这就带来了数据不一致的问题。

对于这种场景,你需要显式地编写事务代码了(注:以下代码适用于EF6):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using ( var context = new MyDbContext())
  {
     using ( var transaction =context.Database.BeginTransaction())
     {
         try
         {
               ……
 
               context.SaveChanges();
 
               context.Database.ExecuteSqlCommand("……);
 
               ……
 
               context.SaveChanges();
               transaction.Commit();
 
          }
         catch (Exception e)
         {
                Console.WriteLine(e.Message);
                transaction.Rollback();
         }
     }
}

 

特别要注意一定要调用commit(),我测试发现,只要不Commit,即使没有异常发生,事务仍将回滚,数据库中的数据不会更新。

第2种情况,你需要使用多个DbContext保存数据

以下是处理这种场景的典型代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void TestTransactionScope2()
 
{
            using (TransactionScope scope = new TransactionScope())
            {
                 String connStr = ……;
                 using ( var conn = newSqlConnection(connStr))
                 {
                     try
                     {
                         conn.Open();
                         using ( var context1 = new MyDbContext(conn, contextOwnsConnection: false ))
                         {
                              ……
                              context1.SaveChanges();
                         }
                       using ( var context2 = new MyDbContext(conn, contextOwnsConnection: false ))
                        {
                            context2.Database.ExecuteSqlCommand(……);
                            context2.SaveChanges();
                        }
                        using ( var context3 = new MyDbContext2(conn, contextOwnsConnection: false ))
                        {
                             ……
                             context3.SaveChanges();
                        }
                       scope.Complete();
                  }
                 catch (Exception e)
                 {
                        Console.WriteLine(e.ToString());
                 }
                 finally
                 {
                         conn.Close();
                  }
             }
       }
}

 

上述代码中有几个关键点:

(1)在构造DbContext对象时,需要把一个己打开的数据库连接对象传给它,并且需要指定EF在DbContext对象销毁时不关闭数据库连接。

为实现此目的,你的DbContext对象应该类似于是这样的,提供两个重载的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyDbContext2 : DbContext
     {
 
        public MyDbContext2(DbConnection conn, boolcontextOwnsConnection): base (conn,contextOwnsConnection)
        {
 
        }
 
        public MyDbContext2(): base ()
        {
 
        }
        public DbSet<OtherEntity> OtherEntities { get ; set ; }
 
        ……
}

 

注意在代码结束时关闭连接。

(2)如果不Commit,则所有数据将不会保存。

(3)你的计算机需要启动MSDTC(分布式交易协调器),请先在控制面板中打开Distributed Transaction Coordinator服务,否则上述代码将在运行时抛出MSDTC服务不可用的异常。

很明显,当事务需要使用多个不同类型的DbContext对象时,Windows需要启动MSDTC,这会对性能有所影响,因此在开发中应该尽量避免这种情况,如无必要,不要在单个事务中使用多个不同种类的DbContext对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值