Entity Framework 6以前,框架本身并没有提供显式的事务处理方案,在EF6中提供了事务处理的API。

所有版本的EF,只要你调用SaveChanges方法进行插入、修改或删除,EF框架会自动将该操作进行事务包装。这种方法无法对事务进行显式的控制,例如新建事务等,可能会造成事务的粒度非常大,降低效率。EF不会对查询进行事务包装。

从EF6开始,默认情况下,如果每次调用Database.ExecuteSqlCommand(),如果其不在存在于任何事务中,则会将该Command包装到一个事务中。框架提供了多种重载,允许你重写这些方法,实现事务的控制。同样,执行存储过程的ObjectContext.ExecuteFunction()方法是实现了这种机制(但是ExecuteFunction不能被重写)。这两种情况下,使用的事务隔离级别均为数据库提供的默认隔离级别,SQL Server中使用的是READ COMMITED。

有同学提供了EF6之前版本的事务方案,如下:

 1 using (BlogDbContext context =new BlogDbContext())
2 {
3 using (TransactionScope transaction =new TransactionScope())
4 {
5 context.BlogPosts.Add(blogPost);
6 context.SaveChanges();
7 postBody.ID = blogPost.ID;
8 context.EntryViewCounts.Add(
9 new EntryViewCount() { EntryID = blogPost.ID });
10 context.PostBodys.Add(postBody);
11 context.SaveChanges();
12 //提交事务
13 transaction.Complete();
14 }
15 }

其实,上面方法执行结果不会错,但是存在隐患,这样情况下,显式事务其实是多余的。所以我对这种方案持怀疑态度(没有进行内部代码的分析,有时间了分析下,希望大家拍砖)。

官方体统的解决方案为:

 using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions; namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var conn = new SqlConnection("..."))
{
conn.Open(); var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery(); using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > );
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
} scope.Complete();
}
}
}
}

一般情况下,用户不需要对事务进行特殊的控制,使用EF框架默认行为即可。如果要对细节进行控制,参考下面章节:

EF6 API工作机制

EF6以前版本EF框架自己管理数据库连接,如果你自己尝试打开连接可能会抛出异常(打开一个已打开的连接会抛出异常)。由于事务必须在一个打开的连接上执行,因此要合并一系列操作到一个事务中,要么使用TractionScope,要么使用ObjectContext.Connection属性直接执行EntityConnection的Open(),并BeginTransaction()。另外,如果你在数据库底层连接上执行了事务,上面API会失败。

注意:EF6中移除了仅接受关闭连接的限制。

EF6 开始提供了:

Database.BeginTransaction() : 为用户提供一种简单易用的方案,在DbContext中启动并完成一个事务 -- 合并一系列操作到该事务中。同时使用户更方便的指定事务隔离级别。

Database.UseTransaction() : 允许DbContext使用一个EF框架外的事务。

在同一DbContext中合并一系列操作到一个事务中

Database.BeginTransaction()有两个重载方法。一个方法提供一个IsolationLevel参数,另一个无参方法使用底层数据库提供程序默认的数据库事务隔离级别。两个重载方法均返回一个DbContextTransaction对象,该对象提供Commit和Rollback方法,用于数据库底层事务的提交和回滚。

使用DbContextTransaction意味着,一旦提交或回滚事务,就要释放该对象。一种简单的方法是使用using语法,在using代码块结束时自动调用该对象的Dispose方法。

 using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions; namespace TransactionsExamples
{
class TransactionsExample
{
static void StartOwnTransactionWithinContext()
{
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand(
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'"
); var query = context.Posts.Where(p => p.Blog.Rating >= );
foreach (var post in query)
{
post.Title += "[Cool Blog]";
} context.SaveChanges(); dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
}
}
}
}

注意:启动一个事务需要底层数据库连接已打开。因此,如果连接未打开,调用Database.BeginTransaction()会打开连接,在其Dispose时关闭连接。

传递一个现有事务到DbContext

有时,你可能需要在同一数据库上,执行一个EF框架之外更大范围的事务,这是就需要自己打开连接并启动事务,然后通知EF框架:

1) 使用已打开的数据库连接

2) 在该连接上使用现有的事务

要实现上面的行为,你需要使用继承自DbContext的构造方法XXXContext(conn,contextOwnsConnection),其中:

conn : 是一个已存在的数据库连接

contextOwnsConnection : 是一个布尔值,指示上下文是否自己占用数据库连接。

注意:这种情况下,contextOwnsConnection必须设置为false,因为它通知EF框架,在自己使用完连接后,不要关闭它。见下面代码:

 using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
}
}

此外,你必须自己启动事务(如果你不想使用默认IsolationLevel,可以自己设置之)并让EF框架知道该连接上已经存在已启动的事务(参考下面代码的33行)。
      然后就可以直接在连接上执行数据库操作,或者在DbContext上执行,所有这些操作均在同一事务中执行,你负责提交或回滚事务,并调用DatabaseTransaction.Dispose(),最后要关闭和释放数据库连接。请参考以下代码:

 using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
sing System.Transactions; namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open(); using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery(); using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn); var query = context.Posts.Where(p => p.Blog.Rating >= );
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
} sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
}
}
}

注意:

  • 你可以传递null到方法Database.UseTransaction()来清除EF框架对当前事务的记忆。如果你这样做,事务既不会提交也不会回滚。所以要谨慎使用之,除非你确实需要这样。
  • 如果EF框架已经持有一个事务,此时你传递一个事务,Database.UseTransaction()将抛出一个异常:

★ EF框架已经持有一个事务;

★ 当EF框架已经在一个TransactionScope中运行;

★ 其数据库连接对象为null (例如,无连接--通常这种情况表示事务已经完成);

★ 数据库连接对象与EF框架的数据库连接对象不匹配;

对TransactionScope的一些补充

如果你使用.net framework 4.5.1及以上版本,可以使用TransactionScope的TransactionScopeAsyncFlowOption参数提供对异步的支持:

 1 using System.Collections.Generic;
2 using System.Data.Entity;
3 using System.Data.SqlClient;
4 using System.Linq;
5 using System.Transactions;
6
7 namespace TransactionsExamples
8 {
9 class TransactionsExample
10 {
11 public static void AsyncTransactionScope()
12 {
13 using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
14 {
15 using (var conn = new SqlConnection("..."))
16 {
17 await conn.OpenAsync();
18
19 var sqlCommand = new SqlCommand();
20 sqlCommand.Connection = conn;
21 sqlCommand.CommandText =
22 @"UPDATE Blogs SET Rating = 5" +
23 " WHERE Name LIKE '%Entity Framework%'";
24 await sqlCommand.ExecuteNonQueryAsync();
25
26 using (var context = new BloggingContext(conn, contextOwnsConnection: false))
27 {
28 var query = context.Posts.Where(p => p.Blog.Rating > 5);
29 foreach (var post in query)
30 {
31 post.Title += "[Cool Blog]";
32 }
33
34 await context.SaveChangesAsync();
35 }
36 }
37 }
38 }
39 }
40 }

目前,使用TransactionScope还有一些限制:

  • 需要.NET 4.5.1及以上版本才支持异步方法;
  • 不能适用于云方案(除非你确保只有一个连接 -- 云方案不支持分布式事务);
  • 不能和Database.UseTransaction()结合使用;
  • 如果你的DDL代码存在问题(例如数据库初始化问题)或没有通过MSDTC服务来支持分布式事务,将抛出异常;

使用TransactionScope的优点:

  • 自动将本地事务升级为分布式事务:前提是你有不止一个连接到给定数据库或要组合一个连接到另一个数据库连接到同一事务(注意:你必须启动MSDTC服务以支持分布式事务)。
  • 易于编程。如果你更希望淡化对事务的关注,而非显示操作事务,使用TransactionScope将是一个更合适的选择。

随着EF6提供了Database.BeginTransaction()和Database.UseTransaction() 两个API,使用TransactionScope不在是必须的了。如果你依然使用TransactionScope,就必须留意上面限制。建议你尽可能使用新的API,而非TransactionScope。

了解Entity Framework中事务处理的更多相关文章

  1. Entity Framework中的多个库操作批量提交、事务处理

    在Entity Framework 中使用SaveChanges()是很频繁的,单次修改或删除数据后调用SaveChanges()返回影响记录数. 要使用批量修改或者批量删除数据,就需要SaveCha ...

  2. Entity Framework 教程——Entity Framework中的实体类型

    Entity Framework中的实体类型 : 在之前的章节中我们介绍过从已有的数据库中创建EDM,它包含数据库中每个表所对应的实体.在EF 5.0/6.0中,存在POCO 实体和动态代理实体两种. ...

  3. 关于Entity Framework中的Attached报错相关解决方案的总结

    关于Entity Framework中的Attached报错的问题,我这里分为以下几种类型,每种类型我都给出相应的解决方案,希望能给大家带来一些的帮助,当然作为读者的您如果觉得有不同的意见或更好的方法 ...

  4. 关于Entity Framework中的Attached报错的完美解决方案终极版

    之前发表过一篇文章题为<关于Entity Framework中的Attached报错的完美解决方案>,那篇文章确实能解决单个实体在进行更新.删除时Attached的报错,注意我这里说的单个 ...

  5. [转]在Entity Framework中使用LINQ语句分页

    本文转自:http://diaosbook.com/Post/2012/9/21/linq-paging-in-entity-framework 我们知道,内存分页效率很低.并且,如果是WebForm ...

  6. 在Entity Framework中使用事务

    继续为想使用Entity Framework的朋友在前面探路,分享的东西虽然技术含量不高,但都是经过实践检验的. 在Entity Framework中使用事务很简单,将操作放在TransactionS ...

  7. LinqToSql和ASP.NET Entity FrameWork 中使用事务

    ASP.NET Entity FrameWork中: int flag = -1; if (this.URPmanagementEntities1.Connection.State != System ...

  8. Lazy<T>在Entity Framework中的性能优化实践

    Lazy<T>在Entity Framework中的性能优化实践(附源码) 2013-10-27 18:12 by JustRun, 328 阅读, 4 评论, 收藏, 编辑 在使用EF的 ...

  9. Entity framework 中Where、First、Count等查询函数使用时要注意

    在.Net开发中,Entity framework是微软ORM架构的最佳官方工具.我们可以使用Lambda表达式在Entity framework中DbSet<T>类上直接做查询(比如使用 ...

随机推荐

  1. ASP.NET MSSQL 依赖缓存设置方法

    更多的时候,我们的服务器性能损耗还是在查询数据库的时候,所以对数据库的缓存还是显得特别重要,上面几种方式都可以实现部分数据缓存功能.但问题是我们的数据有时候是在变化的,这样用户可能在缓存期间查询的数据 ...

  2. 怎样进行Android UI元素设计

    Android UI元素里面包含了许多的内容,比如:该平台由操作系统.中间件.用户界面和应用软件组成,一个应用程序要想受用户喜爱,那么UI可不能差. Android为相似的编程名词引入了一些新的术语, ...

  3. java-Spring-1

    1.@Autowired 自动寻找合适的类型注入,byType2.@Qualifier("userDAOImpl") 存在多个相同类型时,指定固定的一个bean,和上面1配合使用3 ...

  4. python(17) 获取acfun弹幕,评论和视频信息

    每天一点linux命令:新建文件夹

  5. AD7190学习笔记

    1 建议SCL空闲时会高电平. 2复位:上电后连续输入40个1(时钟周期)复位到已知状态,并等待500us后才能访问串行接口,用于SCLK噪音导致的同步. 3单次转换与连续转换(连续读取):每次转换是 ...

  6. RVMDK的DEBUG调试-实时数据查看

    无论在Simulation还是硬件仿真的情况下,View-period windows update后watch窗口添加的变量即可实时更新, 软仿真和硬件仿真的区别就是实际时间的差异:如RTC查看秒的 ...

  7. Vim 中截取部分内容保存到其他文件

    最近无聊,突然想跟着玩玩天池数据挖掘,发现数据好大,想转换到mysql数据库,phpmyadmin import 导入时抱错! 数据文件大大! 于是乎,准备截取一小段到另外一个文件测试先,然后,发现了 ...

  8. C++学习19 类的多继承

    在前面的例子中,派生类都只有一个基类,称为单继承.除此之外,C++也支持多继承,即一个派生类可以有两个或多个基类. 多继承容易让代码逻辑复杂.思路混乱,一直备受争议,中小型项目中较少使用,后来的 Ja ...

  9. [ActionScript 3.0] 根据xml属性查找相应xml节点,递归函数。

    import flash.net.URLLoader; import flash.net.URLRequest; import flash.events.Event; var xml:XML; var ...

  10. [ASP.NET]ASP.NET数据绑定菜单控件

    public void BindMenu(Menu mn,MenuItem menu,string mainMenu,string sql) { MenuItem mitem = null; DB d ...