来自实体框架的SqlException -不允许新事务,因为会话中有其他线程正在运行

我目前得到这个错误:

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 -这个模型位于我们的开发服务器的数据库中。 # EYZ0 < / p >

模型#2 -这个模型位于我们的Prod服务器的数据库中,每天通过自动馈送进行更新。# EYZ0

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

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

对于这个特定的代码,我所要做的就是将DB1中的公司与DB2中的客户机连接起来,从DB2中获取他们的产品列表,并在DB1中插入(如果还没有)。第一次通过应该是充分拉库存。每次在那里运行之后都不会发生任何事情,除非新的库存在夜间进入馈送。

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

259626 次浏览

在拔了很多头发之后,我发现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;
// ...
}

正如您已经确定的那样,您不能在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();
}

这是因为实体框架只支持有序查询的IQueryable<T>.Skip(int),当你考虑到不同范围的多个查询需要稳定的排序时,这是有意义的。如果顺序对您不重要,只需按主键排序,因为主键可能有聚集索引。

这个版本将以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,展示了DB创建的内容。

第一个循环不需要跳过任何内容,因此更简单。

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

我也面临着同样的问题。

这里是原因和解决方法。

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

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

最常见的错误是从db读取数据并返回值的函数。 对于例如isRecordExist.

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

我遇到了同样的问题,但情况不同。我在一个列表框里有一个项目列表。用户可以单击一个项目并选择删除,但我使用存储过程来删除项目,因为删除项目涉及很多逻辑。当我调用存储过程时,删除工作正常,但任何未来调用SaveChanges将导致错误。我的解决方案是调用EF之外的存储过程,这工作得很好。由于某种原因,当我使用EF的方式调用存储过程时,它会留下一些东西。

我们现在已经发布了对该漏洞在Connect上打开的官方回应。我们推荐的变通方法如下:

此错误是由于实体框架在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();
}
}

供你参考:来自一本书,一些行调整,因为它仍然有效:

调用SaveChanges()方法启动一个事务,如果在迭代完成之前发生异常,该事务将自动回滚持久化到数据库的所有更改;否则事务将提交。您可能倾向于在每次实体更新或删除之后应用该方法,而不是在迭代完成之后,特别是当您更新或删除大量实体时。

如果在处理完所有数据之前尝试调用SaveChanges(),则会导致“新事务不允许,因为会话中有其他线程正在运行”;例外。出现异常是因为SQL Server不允许在一个有SqlDataReader打开的连接上启动一个新事务,即使连接字符串启用了多个活动记录集(MARS) (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();
}
所以在项目中,我有这个完全相同的问题,问题不是在foreach.toList(),它实际上是在我们使用的AutoFac配置。 这造成了一些奇怪的情况,上面的错误被抛出,但也抛出了一堆其他等效的错误 这是我们的解决方案: 改变:< / p >
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();

在我的例子中,当我通过EF调用存储过程,然后后来SaveChanges抛出这个异常时出现了问题。问题是在调用过程时,没有处理枚举数。我修复代码如下方式:

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
}
}

我迟到了很久,但今天我遇到了同样的错误,我解决的方法很简单。我的场景类似于这个给定的代码,我在嵌套的for-each循环中制作DB事务。

问题是,单个DB事务比for-each循环花费的时间要长一点,所以一旦之前的事务没有完成,那么新的牵引力就会抛出异常,所以解决方案是在for-each循环中创建一个新对象,在那里您正在进行DB事务。

对于上述场景,解决方案如下:

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 ### //
}
}

我需要读取一个巨大的结果集,并更新表中的一些记录。 我尝试使用块的建议在画Noakes回答.

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

EF创建用于更改检测的数据的第二个副本(因此 它可以将更改持久化到数据库)。EF包含第二个集合 在context的生命周期中,这个集合会耗尽你的资源 内存。< / p >

建议为每个批处理重新创建上下文。

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

下面是我的代码片段:

  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属性的简单结构。

下面是另外两个选项,允许您在for每个循环中调用SaveChanges()。

第一个选项是使用一个DBContext来生成要遍历的列表对象,然后创建第二个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中获取一个数据库对象列表,但是只选择id。然后遍历id列表(假设是int类型),获得与每个int类型对应的对象,并以这种方式调用SaveChanges()。这个方法背后的思想是获取一个大的整数列表,这比获取一个大的db对象列表并对整个对象调用. 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()
}

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

我发现我的查询是错误的,有超过250+编辑等待。所以我修正了我的查询,现在它可以正常工作了。

通过调试查询返回的结果,检查查询是否有错误。之后纠正查询。

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

只需将context.SaveChanges()放在foreach(循环)的末尾。

总是使用你的选择作为列表

例如:

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

然后在保存更改时遍历集合

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


}

我知道这是一个老问题,但我今天遇到了这个错误。

我发现,当数据库表触发器得到一个错误时,这个错误可以被抛出。

作为您的信息,当您遇到这个错误时,您也可以检查您的tables触发器。

如果您由于foreach而得到这个错误,并且您确实需要在循环中首先保存一个实体,并在循环中进一步使用生成的标识,就像我的情况一样,最简单的解决方案是使用另一个DBContext来插入实体,该实体将返回Id并在外部上下文中使用这个Id

例如

    using (var context = new DatabaseContext())
{
...
using (var context1 = new DatabaseContext())
{
...
context1.SaveChanges();
}
//get id of inserted object from context1 and use is.
context.SaveChanges();
}

实际上,在c#中使用实体框架,您无法在foreach循环中保存更改。

context.SaveChanges()方法的行为类似于常规数据库系统(RDMS)上的提交。

只做所有的更改(实体框架将缓存),然后在循环之后(在循环之外)调用SaveChanges()保存所有这些更改,就像数据库提交命令一样。

如果您可以一次保存所有更改,这是可行的。

使你的可查询列表为. tolist(),它应该工作正常。

从EF5迁移到EF6后,我们开始看到这个错误不允许新事务,因为会话中有其他线程在运行

谷歌将我们带到这里,但我们没有在循环中调用SaveChanges()。在使用ObjectContext执行存储过程时引发错误。从DB读取foreach循环中的ExecuteFunction。

任何对ObjectContext的调用。ExecuteFunction将函数包装在事务中。在已经打开读取器的情况下开始事务会导致错误。

可以通过设置以下选项禁用在事务中包装SP。

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

EnsureTransactionsForFunctionsAndCommands选项允许SP在不创建自己的事务的情况下运行,并且不再引发错误。

DbContextConfiguration。EnsureTransactionsForFunctionsAndCommands属性< / >

最近我在我的项目中遇到了同样的问题,所以把我的经验发布出来,它可能会帮助一些和我在同一条船上的人。问题是由于我正在循环EF选择查询的结果(结果没有检索到内存)。

var products = (from e in _context.Products
where e.StatusId == 1
select new { e.Name, e.Type });


foreach (var product in products)
{
//doing some insert EF Queries
//some EF select quries
await _context.SaveChangesAsync(stoppingToken); // This code breaks.
}

我已经更新了我的产品选择查询,将结果带入LIST而不是IQueryable(这似乎是在为每个循环打开阅读器,因此保存失败)。

 var products = (from e in _context.Products
where e.StatusId == 1
select new { e.Name, e.Type })**.ToList()**; //see highlighted

我的情况和上面其他人差不多。我有一个IQueryable,我正在做一个foreach。这又调用了一个带有SaveChanges()的方法。此处出现boom异常,因为上面的查询已经打开了一个事务。

// Example:


var myList = _context.Table.Where(x => x.time == null);


foreach(var i in myList)
{
MyFunction(i); // <<-- Has _context.SaveChanges() which throws exception
}

在我的案例中,将ToList()添加到查询的末尾是解决方案。

// Fix
var myList = _context.Table.Where(x => x.time == null).ToList();