CSharp - SqlException from Entity Framework - 新的事务是不允许的, 因为有其他线程运行

  显示原文与译文双语对照的内容

我目前正收到这里错误:

System.Data.SqlClient.SqlException: 不允许新事务,因为会话中正在运行其他线程。

运行这里代码时:


public class ProductManager : IProductManager
{
 #region Declare Models
 private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
 private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
 #endregion

 public IProduct GetProductById(Guid productId)
 {
//Do a quick sync of the feeds...
 SyncFeeds();
. . .
//get a product...
. . .
 return product;
 }

 private void SyncFeeds()
 {
 bool found = false;
 string feedSource ="AUTO";
 switch (feedSource)//companyFeedDetail.FeedSourceTable.ToUpper())
 {
 case"AUTO":
 var clientList = from a in _dbFeed.Client.Include("Auto") select a;
 foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
 {
 var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
 foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
 {
 if (companyFeedDetail.FeedSourceTable.ToUpper() =="AUTO")
 {
 var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
 foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
 {
 foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
 {
 if (targetProduct.alternateProductID == sourceProduct.AutoID)
 {
 found = true;
 break;
 }
 }
 if (!found)
 {
 var newProduct = new RivWorks.Model.Negotiation.Product();
 newProduct.alternateProductID = sourceProduct.AutoID;
 newProduct.isFromFeed = true;
 newProduct.isDeleted = false;
 newProduct.SKU = sourceProduct.StockNumber;
 company.Product.Add(newProduct);
 }
 }
 _dbRiv.SaveChanges();//### THIS BREAKS ###//
 }
 }
 }
 break;
 }
 }
}

模型 #1 - 这个模型位于我们的开发服务器上的一个数据库中。 Model #1

模型 #2 - 这个模型位于我们的Prod服务器上的一个数据库中,并且每天都通过自动feed更新。 alt text

注- 模型 #1 中的红色圆圈项目是我用于"映射"建模的字段 请忽略模型 #2: 中的红色圆圈 这是另一个问题,现在我已经回答了。

注意:我仍然需要放入一个isDeleted检查,这样我可以从DB1中删除它,如果它已经超出了我们的客户清单。

所有我想做的,与这个特定的代码,是db1连接公司与客户在db2中,得到他们的产品从db2 List 和插入db1如果不是已经存在。 首次通过应该是完整的库存清单。 在每次运行后没有什么会发生,除非新的库存在提要在晚上。

以英镑为代价的问题- 如何解决事务错误我正在得到? 是否需要每次通过循环( 对我来说没有意义) 删除并重新创建上下文?

时间:

经过大量的头发抽出,我发现 foreach 循环是罪魁祸首。 需要发生的事情是调用 EF,但将它返回到目标类型的一个 IList<T>,然后在 IList<T> 上循环。

例如:


IList<Client> clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
 var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
//...
}

就像你已经确定的,你不能从一个仍然在使用活动读取器的数据库中保存。

调用 ToList() 或者 ToArray() 对于小型数据集来说是不错的,但是当你有成千上万的行时,你会消耗大量的内存。

最好以块方式加载行。


public static class EntityFrameworkUtil
{
 public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
 {
 return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
 }

 public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
 {
 int chunkNumber = 0;
 while (true)
 {
 var query = (chunkNumber == 0)
? queryable 
 : queryable.Skip(chunkNumber * chunkSize);
 var chunk = query.Take(chunkSize).ToArray();
 if (chunk.Length == 0)
 yield break;
 yield return chunk;
 chunkNumber++;
 }
 }
}

根据上扩展方法,你可以编写如下查询:


foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
//do stuff
 context.SaveChanges();
}

必须对调用这里方法的可以查询对象进行排序。 这是因为 Entity Framework 只支持排序查询上的IQueryable<T>.Skip(int),当你认为对不同范围的多个查询要求排序时,这就有意义。 如果排序对你来说不重要,那么就可以使用 ORDER BY 主键,因为它有一个聚集索引。

这里版本将成批查询数据库 100. 注意,为每个实体调用 SaveChanges()

如果你想显著提高吞吐量,你应该减少 SaveChanges()的频繁调用。 使用这样的代码:


foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
 foreach (var client in chunk)
 {
//do stuff
 }
 context.SaveChanges();
}

这将导致数据库更新调用的次数减少 100倍。 当然,这些调用需要更长的时间才能完成,但是你仍然在最后。 你的里程可能会变化,但这对我来说是更快速的。

它绕过了你看到的异常。

编辑运行sql分析器后我重新审视这个问题,更新一些东西来提高性能。 对于感兴趣的人,这里有一些示例 SQL,它们展示了数据库创建的内容。

第一个循环不需要跳过任何东西,所以更简单。


SELECT TOP (100) -- the chunk size 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC

后续调用需要跳过以前的结果块,因此引入 row_number的用法:


SELECT TOP (100) -- the chunk size
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM (
 SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
 OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
 FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number]> 100 -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC

我们现在已经发布了的官方响应 Bug 打开的。 我们推荐的解决办法如下:

这里错误是由于 Entity Framework 在 SaveChanges() 调用期间创建隐式事务。 解决这个错误的最好方法是使用不同的模式( 例如,在阅读期间不保存) 或者显式声明一个事务。 以下是三种可能的解决方案:


//1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
 foreach (var person in context.People)
 {
//Change to person
 }
 context.SaveChanges();
}

//2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
 using (var context = new MyContext())
 {
 foreach (var person in context.People)
 {
//Change to person
 context.SaveChanges();
 }
 }
 transaction.Complete();
}

//3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
 var people = context.People.ToList();//Note that this forces the database
//to evaluate the query immediately
//and could be very bad for large tables.

 foreach (var person in people)
 {
//Change to person
 context.SaveChanges();
 }
} 

这是因为你在 foreach 循环中调用 context.SaveChanges(),如前面提到的,应该在 foreach 循环中对实体进行更改,然后在 foreach 循环之后调用 DataContextSaveChanges() 方法。


foreach(var v in vv)
{
 v.ID = xxxx;
 v.fName = xxxx;
 v.lName = xxxx;
}
context.SaveChanges();

我得到了同样的问题但在不同的情况下。 我有一个 List List 物品的盒子。 用户可以单击一项并选择delete删除 but,但我正在使用存储过程删除该项目,因为在删除项目时涉及到大量逻辑。 当我调用存储过程时,删除工作正常,但将来对SaveChanges的调用将导致错误。 我的解决方案是调用在EF之外存储的过程,这很好。 由于某些原因,当我使用高效的方式调用存储过程时,它会留下一些东西。

我也面临同样的问题。

这是原因和解决方案。

http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983. aspx

确保在触发数据操作命令如插入,更新之前,已经关闭所有以前的活动SQL读取器。

最常见的错误是从数据库读取数据和返回值的函数。 对于 e.g 之类的函数。

在这种情况下,如果我们发现记录并忘记关闭读取器,我们会立即从函数返回。

参考:从图书和一些行调整,因为它的有效期是:

调用 SaveChanges() 方法开始一个事务,该事务在迭代完成之前自动回滚到数据库的所有更改;否则,事务将提交。 在每个实体更新或者删除之后,尤其是在你更新或者删除大量实体时,你可能会试图应用该方法,而不是迭代完成。

如果在处理所有数据之前尝试调用 SaveChanges(),则会产生一个"不允许新事务,因为会话中正在运行其他线程"异常。 发生这里异常的原因是,SQL Server 不允许在连接打开的连接上启动新事务,即使连接字符串默认启用了多个活动记录( ef连接字符串启用 MARS )

有时候更好的理解为什么发生了事情;

下面的代码适用于我:


private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
 try
 {
 foreach (var product in _context.products)
 {
 product.pchecked = false;
 _context.products.Attach(product);
 _context.Entry(product).State = EntityState.Modified;
 }
 _context.SaveChanges();
 }
 catch (Exception extofException)
 {
 MessageBox.Show(extofException.ToString());

 }
 productsDataGrid.Items.Refresh();
}

...