Entity framework 回滚事务抛出";已存在与此命令关联的打开的DataReader…“;错误
我正在.NETCore2.0上使用实体框架Core2.0。为了重现这个问题,我制作了一个简单的控制台应用程序Entity framework 回滚事务抛出";已存在与此命令关联的打开的DataReader…“;错误,entity-framework,.net-core,entity-framework-core,Entity Framework,.net Core,Entity Framework Core,我正在.NETCore2.0上使用实体框架Core2.0。为了重现这个问题,我制作了一个简单的控制台应用程序 //Program.cs 静态void Main(字符串[]参数) { 使用(var dbContext=new MyDbContext()) { 使用(var transaction=dbContext.Database.BeginTransaction()) { 尝试 { var blogs=dbContext.blogs .ToList();//由于我的博客类中的架构不匹配而引发错
//Program.cs
静态void Main(字符串[]参数)
{
使用(var dbContext=new MyDbContext())
{
使用(var transaction=dbContext.Database.BeginTransaction())
{
尝试
{
var blogs=dbContext.blogs
.ToList();//由于我的博客类中的架构不匹配而引发错误
//可能会或可能不会对数据库进行更改的其他内容
dbContext.SaveChanges();
Commit();
}
捕获(例外情况除外)
{
transaction.Rollback();//引发隐藏初始错误的第二个错误:“已经有一个打开的DataReader…”
}
}
}
}
我在将Blog类映射到数据库架构时故意犯了一个错误,因此当我在dbContext.Blogs
实体框架上执行.ToList()
时,会抛出一个无效的操作错误,类似于读取Blog.Name的数据库值时发生异常,因为Name
在数据库中是nvarchar(max)
,但在我的Blog
类中是int
因此,现在我的catch
语句尝试在事务期间发生任何错误时回滚事务,但该回滚会导致另一个错误,即最终记录的错误,从而隐藏初始错误
System.InvalidOperationException occurred
HResult=0x80131509
Message=There is already an open DataReader associated with this Command which must be closed first.
Source=<Cannot evaluate the exception source>
StackTrace:
at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
at System.Data.SqlClient.SqlInternalTransaction.Rollback()
at System.Data.SqlClient.SqlTransaction.Rollback()
at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.Rollback()
at ConsoleApp.Program.Main(String[] args) in C:\Users\mitch\OneDrive\Dev\EFCore\ConsoleApp\ConsoleApp\Program.cs:line 28
发生System.InvalidOperationException
HResult=0x80131509
Message=已经有一个与此命令关联的打开的DataReader,必须先关闭它。
来源=
堆栈跟踪:
位于System.Data.SqlClient.SqlInternalConnectionDS.ValidateConnectionForExecute(SqlCommand)
位于System.Data.SqlClient.SqlInternalTransaction.Rollback()处
在System.Data.SqlClient.SqlTransaction.Rollback()处
在Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.Rollback()中
在C:\Users\mitch\OneDrive\Dev\EFCore\ConsoleApp\ConsoleApp\Program.cs中的ConsoleApp.Program.Main(字符串[]args)处:第28行
现在这是一个非常简单的例子,我意识到这里没有什么可承诺的。事实上,我正在运行一个ASP.NET核心应用程序,其中有一个全局事务包装器,因此一些请求可能只是读取,而其他请求可能是读取和更新
我做错什么了吗?据我所知,这是相当标准的,但我已经在谷歌上搜索了两天,没有发现任何人有同样的问题
当EF在为博客查询数据库时打开Data Reader,然后在执行查询中间出现异常,DATaReader将保持打开状态,因此在对该连接进行任何操作之后,我将得到打开的DATALAADER错误。如果是这样,我应该如何处理查询过程中发生的错误?我需要确保可能发生的任何更新都被回滚,并且我需要处理事务和连接。这是System.Data.SqlClient.SqlTransaction中的一个错误,它也会在.NET Framework上重新启动。这也是EF核心中的一个bug,因为它不会在EF6上重新编程。我不确定这是否是一个已知的bug,对于efcore和SqlTransaction,可能会进行不同的分类。最终,Using块将回滚事务
下面是一个最小的ADO.NET复制程序:
using System;
using System.Data.SqlClient;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
using (var con = new SqlConnection("Server=localhost;database=tempdb;Integrated Security=true;MultipleActiveResultsets=false"))
{
con.Open();
using (var tran = con.BeginTransaction())
{
var cmd = new SqlCommand("select * from sys.objects", con, tran);
var rdr = cmd.ExecuteReader();
rdr.Read();
tran.Rollback();
}
}
}
}
}
和最低限度的EF核心复制:
using Microsoft.EntityFrameworkCore;
using System;
using System.Data.SqlClient;
using System.Linq;
namespace ConsoleApp8
{
public class Foo
{
public int Id { get; set; }
}
public class Db : DbContext
{
public DbSet<Foo> Foos { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;database=EfCoreTest;Integrated Security=true;MultipleActiveResultsets=false");
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Foos.Add(new Foo());
db.SaveChanges();
}
using (var db = new Db())
{
var tran = db.Database.BeginTransaction();
foreach (var foo in db.Foos)
{
tran.Rollback();
}
}
}
}
}
我打开了一期GitHub来跟踪这一问题:感谢上帝,有人了解这个问题。我感谢您的详细回复,并感谢您在github上发表文章。我们将考虑使用MARS来解决这个问题,只需要确保没有任何明显的缺陷。它阻止了回滚。这已在2.1版本中修复。@Smit是的,我也在关注github问题。谢谢你的回复。不幸的是,还不知道下一个版本何时发布。
db.Database.ExecuteSqlCommand("if @@trancount > 0 rollback;");