我目前得到这个错误:
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文本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中进行保存。
调用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 Profiler后,我重新审视了这个问题,并更新了一些东西来提高性能。对于感兴趣的人,这里有一些示例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
我需要读取一个巨大的ResultSet并更新表中的一些记录。
我尝试使用Drew Noakes回答中建议的块。
不幸的是,在50000条记录之后,我得到了OutofMemoryException。
答案实体框架大数据集,内存不足的例外解释,即
EF创建用于更改检测的数据的第二个副本
它可以将更改持久化到数据库)。EF包含第二个集合
在context的生命周期中,这个集合会耗尽你的资源
的内存。
建议为每个批处理重新创建上下文。
所以我已经检索了主键的最小值和最大值-表的主键是自动增量整数。然后通过打开每个块的上下文从数据库中检索记录块。处理完后,块上下文关闭并释放内存。它确保内存使用量不会增长。
下面是我的代码片段:
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()
}
从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
大部分答案与循环有关。但我的问题不同。当我试图在同一范围内使用多个dbcontext.Savechanges()命令时,我多次得到错误。
在我的案例中使用ef core 3.1
dbcontext.Database.BeginTransaction ()
而且
dbcontext.Database.CommitTransaction ();
已经解决了问题。以下是我的全部代码:
public IActionResult ApplyForCourse()
{
var master = _userService.GetMasterFromCurrentUser();
var trainee = new Trainee
{
CourseId = courseId,
JobStatus = model.JobStatus,
Gender = model.Gender,
Name = model.Name,
Surname = model.Surname,
Telephone = model.Telephone,
Email = model.Email,
BirthDate = model.BirthDate,
Description = model.Description,
EducationStatus = EducationStatus.AppliedForEducation,
TraineeType = TraineeType.SiteFirst
};
dbcontext.Trainees.Add(trainee);
dbcontext.SaveChanges();
dbcontext.Database.BeginTransaction();
var user = userManager.GetUserAsync(User).Result;
master.TraineeId = trainee.Id;
master.DateOfBirth = model.BirthDate;
master.EducationStatus = trainee.EducationStatus;
user.Gender = model.Gender;
user.Email = model.Email;
dbcontext.Database.CommitTransaction();
dbcontext.SaveChanges();
return RedirectToAction("Index", "Home");
}
}