entity framework中的SqlException – 不允许新的事务,因为会话中还有其他线程正在运行

我目前得到这个错误:

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 – 这个模型位于我们的开发服务器的数据库中。 模型#1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

模型#2 – 此模型位于Prod服务器上的数据库中,并且每天都通过自动提要进行更新。 alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

注 – 模型#1中红圈的项目是我用来“映射”到模型#2的字段。 请忽略模型2中的红色圆圈:这是我现在回答的另一个问题。

注意:我仍然需要放入一个isDeleted检查,以便我可以从DB1软删除它,如果它已经离开我们客户的库存。

我想用这个特定的代码将DB1中的一个公司与DB2中的一个客户端连接起来,从DB2获取它们的产品列表,并将它插入到DB1中,如果它不在那里的话。 第一次通过应该是全面拉动库存。 每次在没有任何事情发生的情况下运行,除非新的库存进入饲料过夜。

所以最大的问题 – 我如何解决我所得到的交易错误? 我是否需要每次通过循环删除和重新创build我的上下文(对我来说没有意义)?

经过多次拔毛后,我发现foreach循环是罪魁祸首。 需要发生的是调用EF,然后将其返回到该目标types的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; // ... } 

正如您已经确定的那样,您不能从仍然通过活动阅读器从数据库中绘制的foreach中进行保存。

调用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) ,当您考虑针对不同范围的多个查询需要稳定的sorting时,这是有意义的。 如果sorting对您不重要,那么只需按主键sorting,因为这可能具有聚集索引。

此版本将以100批为单位查询数据库。请注意,每个实体都调用SaveChanges()

如果要显着提高吞吐量,则应该减less调用SaveChanges()次数。 改用这样的代码:

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

这导致数据库更新调用次数减less了100次。 当然,这些电话中的每一个都需要更长的时间才能完成,但最终还是会走到前面。 你的里程可能会有所不同,但这对我来说世界更快了。

它解决了你所看到的例外情况。

编辑我运行SQL事件探查器后重新访问这个问题,并更新了一些东西,以提高性能。 对于任何感兴趣的人来说,下面是一些示例SQL,显示了由数据库创build的内容。

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

 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 

现在,我们已经发布了一个官方回应Connect上打开的错误 。 我们推荐的解决方法如下:

此错误是由于Entity Framework在SaveChanges()调用期间创build隐式事务。 解决错误的最佳方法是使用不同的模式(即在阅读期间不保存)或通过显式声明事务。 这里有三种可能的解决scheme

 // 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() )。

我得到了同样的问题,但在不同的情况。 我有一个列表框中的项目列表。 用户可以单击一个项目并select删除,但是我正在使用一个存储过程来删除该项目,因为删除该项目涉及到很多逻辑。 当我调用存储过程时,删除工作正常,但将来调用SaveChanges将导致错误。 我的解决scheme是调用外部EF存储的过程,这工作得很好。 由于某种原因,当我使用EF的方式来调用存储过程时,它会让事情开放。

供参考:从一本书和一些调整,因为它的有效性:

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

如果您在处理所有数据之前尝试调用SaveChanges(),则会产生“不允许新的事务,因为会话中有其他线程正在运行”exception。 发生exception是因为SQL Server不允许在SqlDataReader打开的连接上启动新的事务,即使连接string启用了多个活动logging集(MARS)(EF的默认连接string启用了MARS)

有时更好地理解事情发生的原因;-)

所以在这个项目中,我有这个完全相同的问题,问题不在foreach.toList()它实际上是在我们使用的AutoFacconfiguration。 这造成了一些奇怪的情况是上述错误被抛出,但也引发了一堆其他等效的错误。

这是我们的修复:改变了这个:

 container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope(); container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance(); container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest(); 

至:

 container.RegisterType<DataContext>().As<DbContext>().As<DbContext>(); container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope(); container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest(); 

始终将您的select用作列表

例如:

 var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList(); 

然后在保存更改的同时循环播放collections夹

  foreach (var item in tempGroupOfFiles) { var itemToUpdate = item; if (itemToUpdate != null) { itemToUpdate.FileStatusID = 8; itemToUpdate.LastModifiedDate = DateTime.Now; } Entities.SaveChanges(); } 

我也面临同样的问题。

这是原因和解决scheme。

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

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

最常见的错误是从数据库读取数据并返回值的函数。 例如像isRecordExist这样的函数。

在这种情况下,如果我们find了logging,我们立即从函数返回,忘记closures阅读器。

在我的情况下,当我通过EF调用Stored Procedure,然后SaveChanges抛出这个exception时,问题就出现了。 问题在于调用程序,调查员没有处理。 我按照以下方法修复了代码:

 public bool IsUserInRole(string username, string roleName, DataContext context) { var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName); //using here solved the issue using (var en = result.GetEnumerator()) { if (!en.MoveNext()) throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF"); int? resultData = en.Current; return resultData == 1;//1 = success, see T-SQL for return codes } } 

我需要读取一个巨大的ResultSet并更新表中的一些logging。 我试图用Drew Noakes的答案中提到的块。

不幸的是,在50000条logging之后,我得到了OutofMemoryException。 答案Entity框架中的大数据集,内存不足例外解释说

EF创build数据的第二个副本,用于更改检测(以便可以将更改保留到数据库)。 英孚在上下文的整个生命周期中都持有这个第二套,而这个套件则让你失去了记忆。

build议每个批次更新您的上下文。

所以我已经检索了主键的最小值和最大值 – 这些表的主键是自动增量整数。然后通过打开每个块的上下文从数据库中检索数据块。 处理完大块上下文后closures并释放内存。 它确保内存使用不增长。

以下是我的代码片段:

  public void ProcessContextByChunks () { var tableName = "MyTable"; var startTime = DateTime.Now; int i = 0; var minMaxIds = GetMinMaxIds(); for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize) { try { using (var context = InitContext()) { var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize)); try { foreach (var row in chunk) { foundCount = UpdateRowIfNeeded(++i, row); } context.SaveChanges(); } catch (Exception exc) { LogChunkException(i, exc); } } } catch (Exception exc) { LogChunkException(i, exc); } } LogSummaryLine(tableName, i, foundCount, startTime); } private FromToRange<int> GetminMaxIds() { var minMaxIds = new FromToRange<int>(); using (var context = InitContext()) { var allRows = GetMyTableQuery(context); minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0); minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0); } return minMaxIds; } private IQueryable<MyTable> GetMyTableQuery(MyEFContext context) { return context.MyTable; } private MyEFContext InitContext() { var context = new MyEFContext(); context.Database.Connection.ConnectionString = _connectionString; //context.Database.Log = SqlLog; return context; } 

FromToRange是一个具有From和To属性的简单结构。

这里有另外两个选项可以让你在每个循环中调用SaveChanges()。

第一个选项是使用一个DBContext生成您的列表对象迭代,然后创build第二个DBContext调用SaveChanges()。 这里是一个例子:

 //Get your IQueryable list of objects from your main DBContext(db) IQueryable<Object> objects = db.Object.Where(whatever where clause you desire); //Create a new DBContext outside of the foreach loop using (DBContext dbMod = new DBContext()) { //Loop through the IQueryable foreach (Object object in objects) { //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id Object objectMod = dbMod.Object.Find(object.id); //Make whatever changes you need on objectMod objectMod.RightNow = DateTime.Now; //Invoke SaveChanges() on the dbMod context dbMod.SaveChanges() } } 

第二个选项是从DBContext获取数据库对象列表,但只selectID的。 然后迭代id的列表(大概是一个int),并获取对应于每个int的对象,并以这种方式调用SaveChanges()。 这种方法背后的想法是抓住一大堆整数,是更有效的,然后得到一个大的数据库对象列表和调用整个对象的.ToList()。 这是一个这种方法的例子:

 //Get the list of objects you want from your DBContext, and select just the Id's and create a list List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList(); var objects = Ids.Select(id => db.Objects.Find(id)); foreach (var object in objects) { object.RightNow = DateTime.Now; db.SaveChanges() } 

下面的代码适用于我:

 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(); } 

我晚了很多,但今天我面临同样的错误,我的解决方法很简单。 我的情况是类似于这个给定的代码,我是在嵌套for-each循环内进行数据库事务。

问题是单个数据库事务比每个循环花了一点时间,所以一旦前面的事务没有完成,那么新的牵引就会抛出一个exception,所以解决的办法是在for-each循环中创build一个新的对象你正在做一个数据库交易。

对于上述情况,解决scheme将如下所示:

 foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList) { private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString); 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 ### // } } 

我有点晚了,但我也有这个错误。 我通过检查更新的值在哪里解决了这个问题。

我发现我的查询是错误的,并有超过250多个编辑待处理。 所以我纠正了我的查询,现在它工作正确。

所以在我的情况:通过debugging查询返回的结果来检查查询错误。 之后纠正查询。

希望这有助于解决未来的问题。