我在.NET Core 2.0上使用Entity Framework Core 2.0。为了重新创建这个问题,我制作了一个简单的控制台应用程序。
// Program.cs
static void Main(string[] args)
{
using (var dbContext = new MyDbContext())
{
using (var transaction = dbContext.Database.BeginTransaction())
{
try
{
var blogs = dbContext.Blogs
.ToList(); // throws error because of schema mismatch in my Blog class
// other stuff that may or may not make db changes
dbContext.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback(); // throws second error that hides the initial error: "There is already an open DataReader..."
}
}
}
}
我故意将我的Blog类映射到数据库模式时出错,因此当我执行.ToList()
dbContext.Blogs
实体框架时会抛出无效的操作错误,例如{{1} }因为An exception occurred while reading a database value for Blog.Name
在数据库中是Name
,而在nvarchar(max)
类中是int
。
所以现在我的Blog
语句尝试在事务期间发生任何错误时回滚事务,但该回滚会导致另一个错误,即最终被记录的错误,隐藏初始错误。
catch
现在这是一个非常简单的例子,我意识到这里没有什么可以提交的。实际上,我正在运行一个ASP.NET核心应用程序,我有一个全局事务包装器,所以有些请求可能只是读取,但其他请求可能是读取和更新。
我在这里做错了吗?从我所读到的内容来看,这是相当标准的,但我已经在谷歌上搜索了两天而没有发现任何人遇到同样的问题。
当查询数据库中的博客时,EF似乎打开了DataReader,然后在执行查询的过程中发生异常,因此DataReader保持打开状态,所以在此之后对连接做任何事情时,我得到了开放的DataReader错误。如果是这样的话,我该如何处理查询期间发生的错误?我需要确保回滚可能发生的任何更新,并且我需要处理事务和连接。
答案 0 :(得分:4)
这是(IMO)System.Data.SqlClient.SqlTransaction中的一个错误,它也在.NET Framework上进行了重新编译。它也是 EF Core中的一个错误,因为它不会在EF6上重现。我不确定这是否是一个已知的bug,并且可能会对EF Core和SqlTransaction进行不同的分类。最终,您的使用块将回滚事务。
这是一个最小的ADO.NET repro:
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 Core repro:
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();
}
}
}
}
}
有趣的是,这个错误不会在EF6上重现,因为在EF6中DbContext.Database.BeginTransaction使用EntityTransaction,其中EF核心使用轻量级包装器而不是SqlClient的SqlTransation,这具有这种不幸的行为。
要解决此问题,请在连接字符串中添加MultipleActiveResultsets=true
(这在EF中非常有用,因为它允许您在读取结果时运行其他查询)。或者使用TSQL回滚您的交易,如下所示:
db.Database.ExecuteSqlCommand("if @@trancount > 0 rollback;");
我打开了一个GitHub问题来跟踪此问题:https://github.com/aspnet/EntityFrameworkCore/issues/9658