我目前收到此错误:
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 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
模型 #2 - 该模型位于我们的产品服务器上的数据库中,并且每天通过自动提要进行更新。 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 中(如果它还没有的话)。第一次通过应该是一个完整的库存。每次它在那里运行时都不会发生任何事情,除非新的库存在一夜之间进入饲料。
所以最大的问题 - 我如何解决我得到的交易错误?我是否需要每次通过循环删除并重新创建我的上下文(对我来说没有意义)?
在大量拔出头发后,我发现 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();
}
您调用此方法的可查询对象必须是有序的。 这是因为 Entity Framework 仅支持有序查询的 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 Profiler 后重新审视了这个问题并更新了一些东西以提高性能。对于任何感兴趣的人,这里有一些示例 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
我们现已发布对 the bug opened on 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();
}
}
实际上,您无法使用实体框架在 C# 中的 foreach
循环内保存更改。
context.SaveChanges()
方法的作用类似于常规数据库系统 (RDMS) 上的提交。
只需进行所有更改(Entity Framework 将缓存),然后在循环之后(在其外部)调用 SaveChanges()
一次保存所有更改,就像数据库提交命令一样。
如果您可以一次保存所有更改,则此方法有效。
只需将 context.SaveChanges()
放在 foreach
(循环)的末尾即可。
将您的可查询列表设置为 .ToList() ,它应该可以正常工作。
仅供参考:从一本书中调整了一些行,因为它仍然有效:
调用 SaveChanges()
方法会启动一个事务,如果在迭代完成之前发生异常,该事务会自动回滚所有持久化到数据库的更改;否则事务提交。您可能很想在每次实体更新或删除之后应用该方法,而不是在迭代完成之后,尤其是当您更新或删除大量实体时。
如果您尝试在处理完所有数据之前调用 SaveChanges()
,则会引发“不允许新事务,因为会话中正在运行其他线程”异常。发生异常是因为 SQL Server 不允许在打开 SqlDataReader
的连接上启动新事务,即使连接字符串启用了多个活动记录集 (MARS)(EF 的默认连接字符串启用 MARS)
有时最好理解事情发生的原因;-)
始终将您的选择用作列表
例如:
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();
}
我遇到了同样的问题,但情况不同。我在列表框中有一个项目列表。用户可以单击一个项目并选择删除,但我使用存储过程来删除该项目,因为删除该项目涉及很多逻辑。当我调用存储过程时,删除工作正常,但以后对 SaveChanges 的任何调用都会导致错误。我的解决方案是在 EF 之外调用存储的过程,这很好。出于某种原因,当我使用 EF 做事方式调用存储过程时,它会留下一些东西。
SELECT
语句产生了空结果集,如果未读取该结果集,SaveChanges
会抛出该异常。
这里还有另外 2 个选项,允许您在每个循环中调用 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()
}
如果由于 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();
}
从 EF5 迁移到 EF6 后,我们开始看到此错误“不允许新事务,因为会话中正在运行其他线程”。
Google 将我们带到了这里,但我们没有在循环内调用 SaveChanges()
。使用从数据库读取的 foreach 循环内的 ObjectContext.ExecuteFunction 执行存储过程时引发错误。
对 ObjectContext.ExecuteFunction 的任何调用都会将该函数包装在事务中。在已经有打开的读取器时开始事务会导致错误。
可以通过设置以下选项来禁用将 SP 包装在事务中。
_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
EnsureTransactionsForFunctionsAndCommands
选项允许 SP 在不创建自己的事务的情况下运行,并且不再引发错误。
DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands Property
我也面临同样的问题。
这是原因和解决方法。
确保在触发插入、更新等数据操作命令之前,您已经关闭了所有以前的活动 SQL 阅读器。
最常见的错误是从 db 读取数据并返回值的函数。例如,像 isRecordExist 这样的函数。
在这种情况下,如果我们找到记录并忘记关闭阅读器,我们会立即从函数返回。
因此,在项目中,如果我遇到了同样的问题,问题不在 foreach
或 .toList()
中,而是在我们使用的 AutoFac 配置中。这造成了一些奇怪的情况,即引发了上述错误,但也引发了许多其他等效错误。
这是我们的解决方法: 改变了这个:
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();
我知道这是一个老问题,但我今天遇到了这个错误。
我发现,当数据库表触发器出错时,可能会引发此错误。
供您参考,当您收到此错误时,您也可以检查您的表触发器。
我需要读取一个巨大的 ResultSet 并更新表中的一些记录。我尝试按照 Drew Noakes 的 answer 中的建议使用块。
不幸的是,在 50000 条记录之后,我得到了 OutofMemoryException。答案 Entity framework large data set, out of memory exception 解释说,
EF 创建用于更改检测的第二个数据副本(以便它可以将更改持久保存到数据库)。 EF 在上下文的整个生命周期中都保留了第二组,而它的这组会使您的内存不足。
建议为每个批次重新创建上下文。
所以我检索了主键的最小值和最大值——表的主键作为自动增量整数。然后我通过打开每个块的上下文从数据库中检索记录块。处理完块上下文后关闭并释放内存。它确保内存使用量不会增长。
以下是我的代码片段:
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 属性的简单结构。
最近我在我的项目中遇到了同样的问题,所以发布我的经验,它可能会帮助一些和我在同一条船上的人。问题是由于我正在循环浏览 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.
}
我已经更新了我的 Products 选择查询以将结果带入 LIST 而不是 IQueryable (这似乎为每个循环打开了阅读器,因此保存失败)。
var products = (from e in _context.Products
where e.StatusId == 1
select new { e.Name, e.Type })**.ToList()**; //see highlighted
下面的代码对我有用:
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();
}
就我而言,当我通过 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 事务比 for-each 循环花费的时间要长一点,所以一旦之前的事务没有完成,那么新的牵引力就会抛出异常,所以解决方案是在 for-each 循环中创建一个新对象您在哪里进行数据库事务。
对于上述场景,解决方案将如下所示:
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 多个编辑待处理。所以我更正了我的查询,现在它可以正常工作了。
所以在我的情况下:通过调试查询返回的结果来检查查询是否有错误。之后更正查询。
希望这有助于解决未来的问题。
SaveChanges
。因此,另一种解决方案是在循环完成后保存更改。