我目前得到这个错误:

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 loop是罪魁祸首。需要做的是调用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 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

我也面临着同样的问题。

这里是原因和解决方法。

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

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

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

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


I was getting this same issue but in a different situation. I had a list of items in a list box. The user can click an item and select delete but I am using a stored proc to delete the item because there is a lot of logic involved in deleting the item. When I call the stored proc the delete works fine but any future call to SaveChanges will cause the error. My solution was to call the stored proc outside of EF and this worked fine. For some reason when I call the stored proc using the EF way of doing things it leaves something open.


我们现在已经发布了关于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配置。 这造成了一些奇怪的情况,上面的错误被抛出,但也抛出了一堆其他等效的错误。

这是我们的解决方案: 改变:

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

To:

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

我需要读取一个巨大的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()
}

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

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

因此,在我的情况中:通过调试查询返回的结果来检查查询是否有错误。之后纠正查询。

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


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


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

Eg:

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

大部分答案与循环有关。但我的问题不同。当我试图在同一范围内使用多个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");

        }


    }